* [PATCH net v2 01/15] ice: fix removal of PTP timestamp tracker during reset
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
@ 2026-10-08 21:55 ` Tony Nguyen
2026-10-12 23:15 ` netdev-bot+sashiko
2026-10-08 21:55 ` [PATCH net v2 02/15] ice: use reference counting and SRCU for PTP port access Tony Nguyen
` (13 subsequent siblings)
14 siblings, 1 reply; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:55 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Jacob Keller, anthony.l.nguyen, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, poros, richardcochran, horms,
Aleksandr Loktionov, Alexander Nowlin
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, GLOBAL, or EMP
reset will leave the device unable to initiate Tx timestamp requests
indefinitely.
In practice the CORE and GLOBAL resets rarely happen in production
environments, while EMP resets happen after a firmware update that is often
followed by a platform reboot. This 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
Remove the call of ice_ptp_release_tx_tracker() from
ice_ptp_prepare_for_reset(), to keep the tracker memory in place so that
timestamping can resume after a reset.
During review of a previous version of this change, sashiko pointed out
that the teardown flows for ice_ptp_init() and ice_ptp_release() could
potentially leak the PTP timestamp tracker.
Fix ice_ptp_init() so that it allocates the tracker before adding the port
to the port list, and properly calls ice_ptp_release_tx_tracker() as part
of its cleanup on error. Ensure the ps_lock mutex isn't destroyed until the
PF has been cleared from the port list.
Fix ice_ptp_release() so that it handles the cleanup if PTP is in the
error state by cancelling the kworker items and releasing the Tx tracker as
appropriate.
This was found by Sashiko review during feedback for an unrelated change,
and iterated based on further feedback from Sashiko after the initial fix
to remove the call to ice_ptp_release_tx_tracker();
Closes: https://sashiko.dev/#/patchset/20260821-jk-e825c-minimized-fixes-v1-0-9d0731eb4858%40intel.com?part=8
Closes: https://lore.kernel.org/netdev/20260916011213.1632286-1-kuba@kernel.org/
Fixes: 7a25fe5cd5fb ("ice: stop destroying and reinitalizing Tx tracker during reset")
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp.c | 29 ++++++++++++++++--------
1 file changed, 19 insertions(+), 10 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index eaec36ab6ae3..fe21cee4f9de 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -2939,8 +2939,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);
@@ -3347,13 +3345,13 @@ void ice_ptp_init(struct ice_pf *pf)
}
}
- err = ice_ptp_setup_pf(pf);
+ err = ice_ptp_init_port(pf, &ptp->port);
if (err)
- goto err_exit;
+ goto err_destroy_ps_lock;
- err = ice_ptp_init_port(pf, &ptp->port);
+ err = ice_ptp_setup_pf(pf);
if (err)
- goto err_clean_pf;
+ goto err_release_tx_tracker;
/* Start the PHY timestamping block */
ice_ptp_reset_phy_timestamping(pf);
@@ -3365,14 +3363,17 @@ void ice_ptp_init(struct ice_pf *pf)
err = ice_ptp_init_work(pf, ptp);
if (err)
- goto err_exit;
+ goto err_clean_pf;
dev_info(ice_pf_to_dev(pf), "PTP init successful\n");
return;
err_clean_pf:
- mutex_destroy(&ptp->port.ps_lock);
ice_ptp_cleanup_pf(pf);
+err_release_tx_tracker:
+ ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
+err_destroy_ps_lock:
+ mutex_destroy(&ptp->port.ps_lock);
err_exit:
/* If we registered a PTP clock, release it */
if (pf->ptp.clock) {
@@ -3399,12 +3400,20 @@ void ice_ptp_release(struct ice_pf *pf)
return;
if (pf->ptp.state != ICE_PTP_READY) {
- mutex_destroy(&pf->ptp.port.ps_lock);
- ice_ptp_cleanup_pf(pf);
+ if (pf->ptp.kworker) {
+ kthread_cancel_delayed_work_sync(&pf->ptp.work);
+ if (pf->hw.mac_type == ICE_MAC_GENERIC)
+ kthread_cancel_delayed_work_sync(&pf->ptp.port.ov_work);
+ kthread_destroy_worker(pf->ptp.kworker);
+ pf->ptp.kworker = NULL;
+ }
if (pf->ptp.clock) {
ptp_clock_unregister(pf->ptp.clock);
pf->ptp.clock = NULL;
}
+ ice_ptp_cleanup_pf(pf);
+ ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
+ mutex_destroy(&pf->ptp.port.ps_lock);
return;
}
--
2.47.1
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH net v2 01/15] ice: fix removal of PTP timestamp tracker during reset
2026-10-08 21:55 ` [PATCH net v2 01/15] ice: fix removal of PTP timestamp tracker during reset Tony Nguyen
@ 2026-10-12 23:15 ` netdev-bot+sashiko
0 siblings, 0 replies; 28+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 23:15 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
maciej.machnikowski, jacob.e.keller, przemyslaw.korba,
grzegorz.nitka, sergey.temerkhanov, arkadiusz.kubalewski, poros,
richardcochran, horms, pmenzel, aleksandr.loktionov,
alexander.nowlin
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 3 (4) · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The new non-READY (ERROR/RESETTING) branch of ice_ptp_release()
destroys the worker too early.
- [Medium] The patch removes ice_ptp_release_tx_tracker() from
ice_ptp_prepare_for_reset() and puts nothing in its place.
- [Medium] At this commit, when ice_ptp_init_work() fails, the code now
jumps to err_clean_pf.
- [Medium] Now that the Tx tracker survives CORE/GLOBAL/EMP resets, its
per-index tstamps[idx].cached_tstamp and last_ll_ts_idx_read are never…
Pre-existing issues:
- [Medium] The non-READY branch of ice_ptp_release() tears everything
down: kworker, clock, list_del via ice_ptp_cleanup_pf(), the Tx…
- [Medium] At this commit, ice_ptp_flush_all_tx_tracker(),
ice_ptp_restart_all_phy() and ice_ptp_prepare_rebuild_sec() walk…
- [Medium] On E825, ice_ptp_setup_pf() sets this port's bit in the
control PF's shared tx_refclks[phy][tx_clk] map.
- [Medium] Neither the ice_ptp_init() error unwind (after
ice_ptp_reset_phy_timestamping() and ice_ptp_cfg_tx_interrupt()) nor…
--
ice: fix removal of PTP timestamp tracker during reset
This commit stops releasing the PTP Tx timestamp tracker in
ice_ptp_prepare_for_reset(), so timestamping can resume after CORE, GLOBAL
or EMP resets. It reorders ice_ptp_init() so the tracker is allocated
before the port is added to the port list, and the tracker is released on
error. ice_ptp_release() now cancels the kworker and releases the tracker
when PTP is not in the READY state.
Closes: https://sashiko.dev/#/patchset/20260821-jk-e825c-minimized-fixes-v1-0-9d0731eb4858%40intel.com?part=8
Closes: https://lore.kernel.org/netdev/20260916011213.1632286-1-kuba@kernel.org/
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
> index eaec36ab6ae3f..fe21cee4f9dee 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
> @@ -2939,8 +2939,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: Medium]
With this call removed, nothing clears tx->init or calls synchronize_irq()
before a CORE, GLOBAL or EMP reset. Is that intended?
The Tx timestamp paths check only tx->init and tx->calibrating. They never
check ptp->state. Suppose ice_ptp_rebuild() fails:
ice_ptp_rebuild()
err:
ptp->state = ICE_PTP_ERROR;
ice_rebuild() still calls ice_ptp_restore_timestamp_mode()
unconditionally, and that re-enables the Tx timestamp interrupt. After
that, ice_tstamp()->ice_ptp_request_ts() only checks:
if (!ice_ptp_is_tx_tracker_up(tx)) {
So slots are still handed out (skb_get() plus SKBTX_IN_PROGRESS) against a
PHC that isn't running. In the ERROR state cached_phc_jiffies is never
refreshed, so ice_ptp_extend_40b_ts() would discard the result. Before
this patch, these requests were refused and counted in
tx_hwtstamp_skipped.
During the reset window itself, ice_ll_ts_intr() only checks tx->init:
if (tx->init) {
ice_ptp_complete_tx_single_tstamp(tx);
The E810 path in ice_ptp_ts_irq() also checks ice_pf_state_is_nominal().
Can the LL proxy registers now be accessed in the middle of a reset?
The commit message doesn't mention this change in behaviour. Later patches
in the series only add link_up and calibrating gating, so tx->init still
stays set across the reset.
[Severity: Medium]
The tracker now survives the reset, so tstamps[idx].cached_tstamp and
last_ll_ts_idx_read are no longer reset. The E810 PHY timestamp memory is
still reset. Can the two get out of sync?
On rebuild, ice_ptp_rebuild_owner()->ice_ptp_flush_all_tx_tracker()->
ice_ptp_flush_tx_tracker() calls ice_clear_phy_tstamp() for every in_use
index, because tstamp_ready is all ones on E810.
ice_clear_phy_tstamp_e810() then writes 0 to both halves:
err = ice_write_phy_reg_e810(hw, lo_addr, 0);
...
err = ice_write_phy_reg_e810(hw, hi_addr, 0);
E810 has no ready bitmap. It detects a new capture by checking raw_tstamp
!= cached_tstamp. After the reset, a reused index that hasn't captured yet
reads 0, and 0 differs from the stale cached value.
In ice_ptp_process_tx_tstamp(), the missing VALID bit makes it drop the
timestamp and release the slot. Because raw is 0, cached_tstamp isn't
updated, so the later real capture could be credited to the next request
that uses that index.
ice_ptp_complete_tx_single_tstamp() has no VALID check:
if (raw_tstamp == tx->tstamps[idx].cached_tstamp)
return;
tx->tstamps[idx].cached_tstamp = raw_tstamp;
clear_bit(idx, tx->in_use);
It would then pass ice_ptp_extend_40b_ts(pf, 0) to skb_tstamp_tx().
Before 7a25fe5cd5fb the tracker was reallocated with zeroed cached values,
so software and hardware stayed in sync. This is still present at the end
of the series.
> /* Disable periodic outputs */
> ice_ptp_disable_all_perout(pf);
>
> @@ -3347,13 +3345,13 @@ void ice_ptp_init(struct ice_pf *pf)
> }
> }
>
> - err = ice_ptp_setup_pf(pf);
> + err = ice_ptp_init_port(pf, &ptp->port);
> if (err)
> - goto err_exit;
> + goto err_destroy_ps_lock;
>
> - err = ice_ptp_init_port(pf, &ptp->port);
> + err = ice_ptp_setup_pf(pf);
> if (err)
> - goto err_clean_pf;
> + goto err_release_tx_tracker;
>
> /* Start the PHY timestamping block */
> ice_ptp_reset_phy_timestamping(pf);
[Severity: Medium]
This isn't a bug introduced by this patch, but neither the ice_ptp_init()
error unwind nor the non-READY branch of ice_ptp_release() puts the
hardware back into a quiet state.
ice_ptp_reset_phy_timestamping() and ice_ptp_cfg_tx_interrupt() run before
ice_ptp_init_work(). If ice_ptp_init_work() then fails, the unwind never
calls ice_ptp_disable_timestamp_mode(), ice_ptp_port_phy_stop() or the
perout/extts disables.
Can OICR TSYN_TX, PHY timestamping and the SDP outputs stay armed after the
software state has been torn down?
[ ... ]
> @@ -3365,14 +3363,17 @@ void ice_ptp_init(struct ice_pf *pf)
>
> err = ice_ptp_init_work(pf, ptp);
> if (err)
> - goto err_exit;
> + goto err_clean_pf;
[Severity: Medium]
When ice_ptp_init_work() fails, this path now frees the Tx tracker and
destroys ps_lock. However, ptp->state was set just above:
ptp->state = ICE_PTP_READY;
err = ice_ptp_init_work(pf, ptp);
if (err)
goto err_clean_pf;
The PHC is also still registered at this point. Could ice_ptp_link_change(),
ice_ptp_queue_work() or the PHC callbacks touch the freed tracker or the
destroyed mutex in this window? One example is
ice_ptp_reset_cached_phctime()->ice_ptp_mark_tx_tracker_stale().
The NULL kworker exposure while READY was already there before. Later in
the series, ice_ptp_init() creates the kworker earlier and sets
ICE_PTP_READY only after ice_ptp_init_work() succeeds, which appears to
close this. At this commit the window is still open.
>
> dev_info(ice_pf_to_dev(pf), "PTP init successful\n");
> return;
>
> err_clean_pf:
> - mutex_destroy(&ptp->port.ps_lock);
> ice_ptp_cleanup_pf(pf);
[Severity: Medium]
This is a pre-existing issue, but on E825 ice_ptp_setup_pf() sets this
port's bit in the control PF's shared map:
set_bit(port_num,
&ctrl_ptp->tx_refclks[phy][pf->ptp.port.tx_clk]);
ice_ptp_cleanup_pf() only does list_del() and never clears that bit. After
this error path, or after an unbind, would ice_txclk_any_port_uses() still
count this port as a user of the reference clock?
> +err_release_tx_tracker:
> + ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
> +err_destroy_ps_lock:
> + mutex_destroy(&ptp->port.ps_lock);
> err_exit:
[ ... ]
> @@ -3399,12 +3400,20 @@ void ice_ptp_release(struct ice_pf *pf)
> return;
>
> if (pf->ptp.state != ICE_PTP_READY) {
> - mutex_destroy(&pf->ptp.port.ps_lock);
> - ice_ptp_cleanup_pf(pf);
> + if (pf->ptp.kworker) {
> + kthread_cancel_delayed_work_sync(&pf->ptp.work);
> + if (pf->hw.mac_type == ICE_MAC_GENERIC)
> + kthread_cancel_delayed_work_sync(&pf->ptp.port.ov_work);
> + kthread_destroy_worker(pf->ptp.kworker);
> + pf->ptp.kworker = NULL;
> + }
[Severity: High]
The worker is destroyed here and pf->ptp.kworker is set to NULL. The PHC is
only unregistered further down, and the port stays on
adapter->ports.ports until ice_ptp_cleanup_pf() runs. Can anything still
reach this worker in between?
On ICE_MAC_GENERIC, ice_ptp_settime64() calls ice_ptp_restart_all_phy()
without checking ptp->state. ice_ptp_adjtime() does the same through its
non-atomic path. ice_ptp_restart_all_phy() walks the list without
ports.lock:
list_for_each(entry, &pf->adapter->ports.ports) {
...
if (port->link_up)
ice_ptp_port_phy_restart(port);
}
ice_ptp_port_phy_restart() then does:
case ICE_MAC_GENERIC:
kthread_cancel_delayed_work_sync(&ptp_port->ov_work);
...
kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work,
0);
Here pf is the releasing PF. That covers the releasing PF's own port, and
also its port when it is a peer and the owner PF is the one calling
settime.
ice_ptp_reset_cached_phctime() also queues on pf->ptp.kworker when
ICE_CFG_BUSY is set, with no NULL or state check.
Before this patch the non-READY branch leaked the worker rather than
freeing it, so this NULL dereference or use-after-free of the
kthread_worker looks new. It can be hit if a PF in ERROR or RESETTING
state is unbound while userspace calls clock_settime() on the owner PHC.
Could the clock be unregistered and the port removed from the list before
the worker is destroyed? The commit message already describes that
ordering for ps_lock. The ordering here is unchanged at the end of the
series.
> if (pf->ptp.clock) {
> ptp_clock_unregister(pf->ptp.clock);
> pf->ptp.clock = NULL;
> }
> + ice_ptp_cleanup_pf(pf);
> + ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
[Severity: Medium]
This is a pre-existing issue, but three functions walk
adapter->ports.ports without ports.lock: ice_ptp_flush_all_tx_tracker(),
ice_ptp_restart_all_phy() and ice_ptp_prepare_rebuild_sec(). For example:
list_for_each_entry(port, &pf->adapter->ports.ports, list_node)
ice_ptp_flush_tx_tracker(ptp_port_to_pf(port), &port->tx);
Meanwhile ice_ptp_cleanup_pf() unlinks the port under the lock, and
ice_ptp_release_tx_tracker() then frees the tracker. Trackers now stay
allocated across CORE/GLOBAL resets, so the owner's rebuild-time flush
works on live peer trackers. Can one of these walkers race with this
teardown?
A later patch in this series, "ice: use reference counting and SRCU for
PTP port access", converts these walkers to SRCU with kref and appears to
close this race.
> + mutex_destroy(&pf->ptp.port.ps_lock);
> return;
> }
[Severity: Medium]
This isn't a bug introduced by this patch, but this branch tears
everything down and returns without setting pf->ptp.state to
ICE_PTP_UNINIT. If ice_ptp_release() runs again before ice_ptp_init()
resets the state, would ice_ptp_cleanup_pf() call list_del() on a node
that already holds LIST_POISON pointers?
One possible sequence:
1. devlink reinit_down runs while PTP is in ERROR or RESETTING, which goes
through ice_unload()->ice_ptp_release().
2. reinit_up fails before ice_load().
3. ice_remove()->ice_unload()->ice_deinit_features()->ice_ptp_release()
runs again.
The old branch had the same list_del() without a state change, and the end
of the series still doesn't set ICE_PTP_UNINIT here.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008215614.1987250-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH net v2 02/15] ice: use reference counting and SRCU for PTP port access
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
2026-10-08 21:55 ` [PATCH net v2 01/15] ice: fix removal of PTP timestamp tracker during reset Tony Nguyen
@ 2026-10-08 21:55 ` Tony Nguyen
2026-10-12 23:15 ` netdev-bot+sashiko
2026-10-08 21:56 ` [PATCH net v2 03/15] ice: fix PHY port restart serialization Tony Nguyen
` (12 subsequent siblings)
14 siblings, 1 reply; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:55 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Jacob Keller, anthony.l.nguyen, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, poros, richardcochran, horms,
Alexander Nowlin
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 SRCU primitives for the port linked list, along with a
reference count on the port. The kref reference counter ensures that a PF
will have a valid lifetime and not be removed until the reference is
released. Note that this change focuses solely on the port list and does
not make an effort to resolve access to ctrl_pf, which is currently being
investigated by another developer.
Use of sleepable RCU is required because we often iterate the PTP port list
and perform operations that might sleep. Attempts at implementing regular
RCU have thus far not proven to be acceptable.
The remove path first removes the port from the list, and we use
kref_get_unless_zero to ensure that such ports are skipped when iterating
the list. This ensures that once a port starts removing we will drop
references and no longer be able to acquire new ones. This avoids loop
iterations chaining together to indefinitely block removal.
The ice_ptp_release_port_srcu() function is used as the release function for
the kref_put() call. This uses wake_up_var() to wake the removing thread.
The waiting thread will block until the final reference has been removed,
then it will use synchronize_srcu() to ensure any outstanding SRCU critical
sections have had the necessary grace period.
This flow ensures that accesses to ports via the adapter port list will
remain valid until both the SRCU critical sections have ended and all the
references to the ports have been dropped. Strictly speaking, SRCU alone
might be sufficient for existing code paths, but the reference count allows
the option for passing a pointer to the port on to other functions if
necessary in the future.
Fixes: e800654e85b5 ("ice: Use ice_adapter for PTP shared data instead of auxdev")
Reviewed-by: Maciek Machnikowski <maciej.machnikowski@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_adapter.c | 17 ++-
drivers/net/ethernet/intel/ice/ice_adapter.h | 16 ++-
drivers/net/ethernet/intel/ice/ice_ptp.c | 121 ++++++++++++++-----
drivers/net/ethernet/intel/ice/ice_ptp.h | 4 +
4 files changed, 116 insertions(+), 42 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.c b/drivers/net/ethernet/intel/ice/ice_adapter.c
index 2dc3629d6d0f..536923b6ae97 100644
--- a/drivers/net/ethernet/intel/ice/ice_adapter.c
+++ b/drivers/net/ethernet/intel/ice/ice_adapter.c
@@ -6,6 +6,7 @@
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
+#include <linux/srcu.h>
#include <linux/xarray.h>
#include "ice_adapter.h"
#include "ice.h"
@@ -54,11 +55,18 @@ static unsigned long ice_adapter_xa_index(struct pci_dev *pdev)
static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev)
{
struct ice_adapter *adapter;
+ int err;
adapter = kzalloc_obj(*adapter);
if (!adapter)
return NULL;
+ err = init_srcu_struct(&adapter->ports.srcu);
+ if (err) {
+ kfree(adapter);
+ return NULL;
+ }
+
adapter->index = ice_adapter_index(pdev);
spin_lock_init(&adapter->ptp_gltsyn_time_lock);
spin_lock_init(&adapter->txq_ctx_lock);
@@ -66,18 +74,19 @@ 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);
+
+ cleanup_srcu_struct(&adapter->ports.srcu);
kfree(adapter);
}
diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.h b/drivers/net/ethernet/intel/ice/ice_adapter.h
index 4f695f32da3d..0b01c7f5cf0d 100644
--- a/drivers/net/ethernet/intel/ice/ice_adapter.h
+++ b/drivers/net/ethernet/intel/ice/ice_adapter.h
@@ -17,15 +17,19 @@ struct ice_pf;
/**
* struct ice_port_list - data used to store the list of adapter ports
*
- * This structure contains data used to maintain a list of adapter ports
+ * This structure contains data used to maintain a list of adapter ports.
+ * Writers modifying the list *must* acquire the lock, and use SRCU safe list
+ * operations. Readers should use srcu_read_lock() on the provided domain.
*
- * @ports: list of ports
- * @lock: protect access to the ports list
+ * @list: list of ports
+ * @lock: protect write access to the list
+ * @srcu: Sleepable RCU domain for this adapter
*/
struct ice_port_list {
- struct list_head ports;
- /* To synchronize the ports list operations */
- struct mutex lock;
+ struct list_head list;
+ /* To synchronize write operations on the port list */
+ spinlock_t lock;
+ struct srcu_struct srcu;
};
/**
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index fe21cee4f9de..94a66e9d8c05 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2021, Intel Corporation. */
+#include <linux/rculist.h>
+#include <linux/srcu.h>
+#include <linux/wait_bit.h>
#include "ice.h"
#include "ice_lib.h"
#include "ice_trace.h"
@@ -673,20 +676,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_srcu(struct kref *ref)
+{
+ wake_up_var(ref);
+}
+
static void ice_ptp_tx_tstamp_owner(struct ice_pf *pf)
{
+ struct ice_port_list *ports = &pf->adapter->ports;
struct ice_ptp_port *port;
+ int srcu_idx;
- mutex_lock(&pf->adapter->ports.lock);
- list_for_each_entry(port, &pf->adapter->ports.ports, list_node) {
+ srcu_idx = srcu_read_lock(&ports->srcu);
+ list_for_each_entry_srcu(port, &ports->list, list_node,
+ srcu_read_lock_held(&ports->srcu)) {
struct ice_ptp_tx *tx = &port->tx;
- if (!tx || !tx->init)
+ if (!tx->init)
+ continue;
+
+ if (!kref_get_unless_zero(&port->ref))
continue;
ice_ptp_process_tx_tstamp(tx);
+
+ kref_put(&port->ref, ice_ptp_release_port_srcu);
}
- mutex_unlock(&pf->adapter->ports.lock);
+ srcu_read_unlock(&ports->srcu, srcu_idx);
}
/**
@@ -806,10 +822,19 @@ ice_ptp_mark_tx_tracker_stale(struct ice_ptp_tx *tx)
static void
ice_ptp_flush_all_tx_tracker(struct ice_pf *pf)
{
+ struct ice_port_list *ports = &pf->adapter->ports;
struct ice_ptp_port *port;
+ int srcu_idx;
- list_for_each_entry(port, &pf->adapter->ports.ports, list_node)
+ srcu_idx = srcu_read_lock(&ports->srcu);
+ list_for_each_entry_srcu(port, &ports->list, list_node,
+ srcu_read_lock_held(&ports->srcu)) {
+ if (!kref_get_unless_zero(&port->ref))
+ continue;
ice_ptp_flush_tx_tracker(ptp_port_to_pf(port), &port->tx);
+ kref_put(&port->ref, ice_ptp_release_port_srcu);
+ }
+ srcu_read_unlock(&ports->srcu, srcu_idx);
}
/**
@@ -1424,16 +1449,22 @@ 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_port_list *ports = &pf->adapter->ports;
+ struct ice_ptp_port *port;
+ int srcu_idx;
- list_for_each(entry, &pf->adapter->ports.ports) {
- struct ice_ptp_port *port = list_entry(entry,
- struct ice_ptp_port,
- list_node);
+ srcu_idx = srcu_read_lock(&ports->srcu);
+ list_for_each_entry_srcu(port, &ports->list, list_node,
+ srcu_read_lock_held(&ports->srcu)) {
+ if (!kref_get_unless_zero(&port->ref))
+ continue;
if (port->link_up)
ice_ptp_port_phy_restart(port);
+
+ kref_put(&port->ref, ice_ptp_release_port_srcu);
}
+ srcu_read_unlock(&ports->srcu, srcu_idx);
}
/**
@@ -2694,19 +2725,28 @@ static bool ice_port_has_timestamps(struct ice_ptp_tx *tx)
static bool ice_any_port_has_timestamps(struct ice_pf *pf)
{
+ struct ice_port_list *ports = &pf->adapter->ports;
+ bool have_tstamps = false;
struct ice_ptp_port *port;
+ int srcu_idx;
- 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;
+ srcu_idx = srcu_read_lock(&ports->srcu);
+ list_for_each_entry_srcu(port, &ports->list, list_node,
+ srcu_read_lock_held(&ports->srcu)) {
+ if (!kref_get_unless_zero(&port->ref))
+ continue;
- if (ice_port_has_timestamps(tx))
- return true;
- }
+ if (ice_port_has_timestamps(&port->tx))
+ have_tstamps = true;
+
+ kref_put(&port->ref, ice_ptp_release_port_srcu);
+
+ if (have_tstamps)
+ break;
}
+ srcu_read_unlock(&ports->srcu, srcu_idx);
- return false;
+ return have_tstamps;
}
bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
@@ -2890,14 +2930,18 @@ 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_port_list *ports = &pf->adapter->ports;
+ struct ice_ptp_port *port;
+ int srcu_idx;
- list_for_each(entry, &pf->adapter->ports.ports) {
- struct ice_ptp_port *port = list_entry(entry,
- struct ice_ptp_port,
- list_node);
+ srcu_idx = srcu_read_lock(&ports->srcu);
+ list_for_each_entry_srcu(port, &ports->list, list_node,
+ srcu_read_lock_held(&ports->srcu)) {
struct ice_pf *peer_pf = ptp_port_to_pf(port);
+ if (!kref_get_unless_zero(&port->ref))
+ continue;
+
if (!ice_is_primary(&peer_pf->hw)) {
if (rebuild) {
/* TODO: When implementing rebuild=true:
@@ -2909,7 +2953,10 @@ static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild,
ice_ptp_prepare_for_reset(peer_pf, reset_type);
}
}
+
+ kref_put(&port->ref, ice_ptp_release_port_srcu);
}
+ srcu_read_unlock(&ports->srcu, srcu_idx);
}
/**
@@ -3084,11 +3131,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
@@ -3110,13 +3157,23 @@ static int ice_ptp_setup_pf(struct ice_pf *pf)
static void ice_ptp_cleanup_pf(struct ice_pf *pf)
{
+ struct ice_port_list *ports = &pf->adapter->ports;
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;
+
+ spin_lock(&ports->lock);
+ list_del_rcu(&ptp->port.list_node);
+ spin_unlock(&ports->lock);
+
+ ref = &ptp->port.ref;
+ kref_put(ref, ice_ptp_release_port_srcu);
+
+ wait_var_event(ref, !kref_read(ref));
+
+ synchronize_srcu(&ports->srcu);
}
/**
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
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH net v2 02/15] ice: use reference counting and SRCU for PTP port access
2026-10-08 21:55 ` [PATCH net v2 02/15] ice: use reference counting and SRCU for PTP port access Tony Nguyen
@ 2026-10-12 23:15 ` netdev-bot+sashiko
0 siblings, 0 replies; 28+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 23:15 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
maciej.machnikowski, jacob.e.keller, przemyslaw.korba,
grzegorz.nitka, sergey.temerkhanov, arkadiusz.kubalewski, poros,
richardcochran, horms, pmenzel, aleksandr.loktionov,
alexander.nowlin
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 1 (2) · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] In the non-READY branch of ice_ptp_release() (state
ICE_PTP_RESETTING or ICE_PTP_ERROR), the PF cancels its work, calls…
- [Medium] Lost-wakeup hang in ice_ptp_cleanup_pf()
(drivers/net/ethernet/intel/ice/ice_ptp.c).
Pre-existing issues:
- [Medium] ice_ptp_cleanup_pf() cannot safely run twice, and the non-
READY branch of ice_ptp_release() can run it twice.
- [Medium] ice_ptp_prepare_rebuild_sec() (E825 clock owner, non-PFR
reset) calls ice_ptp_prepare_for_reset(peer_pf) while holding only…
--
ice: use reference counting and SRCU for PTP port access
This patch replaces the mutex that protects the ice adapter PTP port list
with an SRCU-protected list and adds a kref to each ice_ptp_port. Readers
skip ports whose kref has already dropped to zero.
ice_ptp_cleanup_pf() unlinks the port, drops the initial reference, waits
for the count to reach zero with wait_var_event(), and then calls
synchronize_srcu().
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
> index fe21cee4f9dee..94a66e9d8c05d 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
[ ... ]
> @@ -673,20 +676,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_srcu(struct kref *ref)
> +{
> + wake_up_var(ref);
> +}
[Severity: Medium]
Is a full memory barrier needed before this wake_up_var()?
The kernel-doc for wake_up_var() in kernel/sched/wait_bit.c says:
In order for this to function properly there must be a full memory
barrier after the variable is updated ... If the variable was updated
by a fully ordered operation such as atomic_dec_and_test() then no
extra barrier is required. Otherwise smb_mb() is needed.
kref_put() goes through refcount_dec_and_test(). That is
atomic_fetch_sub_release() followed by smp_acquire__after_ctrl_dep() on
success, so it gives release ordering plus acquire on success. It is not
fully ordered. __wake_up_bit() then checks waitqueue_active() without
taking the waitqueue lock.
Can this interleaving happen on a weakly ordered architecture such as
arm64?
CPU1 (last reader, e.g. ice_ptp_tx_tstamp_owner)
kref_put()
refcount_dec_and_test() /* refs 1 -> 0, release only */
ice_ptp_release_port_srcu()
wake_up_var(ref)
waitqueue_active() sees an empty queue
CPU2 (ice_ptp_cleanup_pf)
wait_var_event(ref, !kref_read(ref))
adds itself to the waitqueue, set_current_state()
kref_read(ref) still returns 1
schedule()
If so, the remover sleeps in TASK_UNINTERRUPTIBLE with no timeout.
var_wake_function() filters on the key, so an unrelated wakeup on the
same hashed queue would not wake it.
Would adding smp_mb__after_atomic() before wake_up_var(), or using a
fully ordered decrement, avoid this?
> static void ice_ptp_tx_tstamp_owner(struct ice_pf *pf)
> {
[ ... ]
> @@ -1424,16 +1449,22 @@ 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_port_list *ports = &pf->adapter->ports;
> + struct ice_ptp_port *port;
> + int srcu_idx;
>
> - list_for_each(entry, &pf->adapter->ports.ports) {
> - struct ice_ptp_port *port = list_entry(entry,
> - struct ice_ptp_port,
> - list_node);
> + srcu_idx = srcu_read_lock(&ports->srcu);
> + list_for_each_entry_srcu(port, &ports->list, list_node,
> + srcu_read_lock_held(&ports->srcu)) {
> + if (!kref_get_unless_zero(&port->ref))
> + continue;
>
> if (port->link_up)
> ice_ptp_port_phy_restart(port);
[Severity: High]
Can this loop reach a port whose PF has already destroyed its kthread
worker?
When the state is ICE_PTP_RESETTING or ICE_PTP_ERROR, ice_ptp_release()
takes its non-READY branch. That branch tears down the worker and the
clock before the port is removed from the list:
ice_ptp_release() {
...
if (pf->ptp.state != ICE_PTP_READY) {
if (pf->ptp.kworker) {
kthread_cancel_delayed_work_sync(&pf->ptp.work);
...
kthread_destroy_worker(pf->ptp.kworker);
pf->ptp.kworker = NULL;
}
if (pf->ptp.clock) {
ptp_clock_unregister(pf->ptp.clock);
pf->ptp.clock = NULL;
}
ice_ptp_cleanup_pf(pf);
...
}
ice_ptp_cleanup_pf() is the only place that unlinks the port, so
kref_get_unless_zero() here still succeeds until it runs. port->link_up
can also still be true, because only ice_ptp_link_change() rewrites it,
and only in the READY state.
For ICE_MAC_GENERIC, ice_ptp_port_phy_restart() then does:
kthread_cancel_delayed_work_sync(&ptp_port->ov_work);
...
kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work,
0);
kthread_destroy_worker() frees the worker but does not clear
ov_work.work.worker. The cancel would therefore take the lock of a freed
worker. The queue call would then pass a NULL pf->ptp.kworker.
The clock owner can hit this through ice_ptp_rebuild_owner(). Because
ptp_clock_unregister() can sleep, the window is wider than it looks.
The same window seems to affect ice_ptp_prepare_rebuild_sec(). It calls
ice_ptp_prepare_for_reset(peer_pf), which runs
kthread_cancel_delayed_work_sync(&ptp->work) on the peer.
The READY branch already calls ice_ptp_cleanup_pf() before it cancels
any work. Should the non-READY branch also call ice_ptp_cleanup_pf()
before destroying the worker?
The commit message says the kref "ensures that a PF will have a valid
lifetime and not be removed until the reference is released". It also
lists ice_ptp_restart_all_phy() as one of the iterations being fixed.
> +
> + kref_put(&port->ref, ice_ptp_release_port_srcu);
> }
> + srcu_read_unlock(&ports->srcu, srcu_idx);
> }
[ ... ]
> @@ -2890,14 +2930,18 @@ 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)
> {
[ ... ]
> + if (!kref_get_unless_zero(&port->ref))
> + continue;
> +
> if (!ice_is_primary(&peer_pf->hw)) {
> if (rebuild) {
> /* TODO: When implementing rebuild=true:
> @@ -2909,7 +2953,10 @@ static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild,
> ice_ptp_prepare_for_reset(peer_pf, reset_type);
> }
> }
[Severity: Medium]
This is a pre-existing issue, but does anything here serialize the peer's
ptp->state against the peer's own ice_ptp_release()?
ice_ptp_prepare_for_reset() checks and sets the peer's state without a
lock:
if (ptp->state != ICE_PTP_READY)
return;
ptp->state = ICE_PTP_RESETTING;
/* Disable timestamping for both Tx and Rx */
ice_ptp_disable_timestamp_mode(pf);
kthread_cancel_delayed_work_sync(&ptp->work);
At the same time, ice_ptp_release() on the peer reads this field and
writes ICE_PTP_UNINIT to it.
If the owner writes RESETTING after the peer has written UNINIT, the
peer exits release in a non-READY state. A later ice_ptp_release() would
then repeat the cleanup.
In the opposite order, the peer takes the non-READY branch and destroys
its kworker while the owner is about to cancel work on it. That is the
same ordering problem described above for ice_ptp_restart_all_phy().
Calling ice_ptp_cleanup_pf() first in that branch would fix this case
too.
The kref and SRCU here protect the port memory and the list linkage. They
do not protect the PF state machine. The commit message notes that the
change "focuses solely on the port list".
> +
> + kref_put(&port->ref, ice_ptp_release_port_srcu);
> }
> + srcu_read_unlock(&ports->srcu, srcu_idx);
> }
[ ... ]
> @@ -3110,13 +3157,23 @@ static int ice_ptp_setup_pf(struct ice_pf *pf)
>
> static void ice_ptp_cleanup_pf(struct ice_pf *pf)
> {
[ ... ]
> + spin_lock(&ports->lock);
> + list_del_rcu(&ptp->port.list_node);
> + spin_unlock(&ports->lock);
> +
> + ref = &ptp->port.ref;
> + kref_put(ref, ice_ptp_release_port_srcu);
> +
> + wait_var_event(ref, !kref_read(ref));
[Severity: Medium]
This isn't a bug introduced by this patch, but what happens if
ice_ptp_cleanup_pf() runs twice for the same PF?
The non-READY branch of ice_ptp_release() calls ice_ptp_cleanup_pf() and
returns without setting ptp.state to ICE_PTP_UNINIT. This sequence seems
to run the cleanup twice:
ice_devlink_reinit_down()->ice_unload()->ice_deinit_features()
->ice_ptp_release()
(state is ICE_PTP_ERROR or ICE_PTP_RESETTING, cleanup #1)
ice_devlink_reinit_up() fails before ice_init_features()
(ice_ptp_init() never resets the state)
ice_remove()->ice_unload()->...->ice_ptp_release()
(cleanup #2)
Before this patch, the second list_del() on a poisoned node either
faulted or, with LIST_HARDENED/DEBUG_LIST, warned and was skipped.
With this patch, the second kref_put() runs on a kref that is already
zero. refcount_dec_and_test() warns and saturates the counter without
calling the release function.
kref_read() then never returns zero. wait_var_event() would block
uninterruptibly forever while devl_lock is held. On hardened-list
kernels, the result changes from a warning to a hang.
Should the non-READY branch move the state to ICE_PTP_UNINIT, or should
ice_ptp_cleanup_pf() be made safe to call twice?
> +
> + synchronize_srcu(&ports->srcu);
> }
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008215614.1987250-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH net v2 03/15] ice: fix PHY port restart serialization
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
2026-10-08 21:55 ` [PATCH net v2 01/15] ice: fix removal of PTP timestamp tracker during reset Tony Nguyen
2026-10-08 21:55 ` [PATCH net v2 02/15] ice: use reference counting and SRCU for PTP port access Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-12 23:15 ` netdev-bot+sashiko
2026-10-08 21:56 ` [PATCH net v2 04/15] ice: set in_use only after preparing Tx timestamp index Tony Nguyen
` (11 subsequent siblings)
14 siblings, 1 reply; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Jacob Keller, anthony.l.nguyen, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, poros, richardcochran, horms,
Alexander Nowlin
From: Jacob Keller <jacob.e.keller@intel.com>
The ice_port_phy_restart() function is responsible for restarting a PHY
port, primarily after a link transition. PHY ports must also all be
restarted after a reset, and the E822 devices also restart the PHY after
the .settime64 operation.
If a PHY restart occurs concurrently with a link transition, it is possible
that the link state read could be re-ordered such that the link transition
would start the PHY but a concurrent restart (such as from .settime64)
could see the old link state and decide it must stop the PHY.
This occurs because the restart procedure depends on the link state value
but setting that value is not serialized with the per-port ps_lock. A
following fix for E825 is going to modify the .setttime64 to also correctly
restart the PHY timers, opening the E825 device to a race between
.settime64 and a concurrent link change.
To avoid this, we need to ensure that the port link_up field is set within
the same critical section as the restart procedure. This way we ensure that
the end result is the PHY programmed to the correct state.
Additionally note that the PHY restart procedure already cannot run
concurrently across two ports. The procedure requires executing timer
commands which in turn require the PTP hardware semaphore.
Thus, to simplify the locking behavior, convert the per-port ps_lock to a
single mutex in the ice_adapter. Acquire this in ice_ptp_restart_all_phy(),
and hold it while calling ice_ptp_port_phy_stop() and
ice_ptp_port_phy_restart().
The ice_ptp_link_change() function now acquires the port lock for the
entire change. This ensures that the link state is set under lock. Note the
function does also acquire the dplls.lock for E825 devices. This ordering
is safe because the other callers of dplls.lock do not acquire the
ps_lock.
The switch to a single lock for the adapter instead of one per port is
easier to reason about. It also could be a first step in a plan to replace
the PTP hardware semaphore completely, which is under investigation for the
future.
This fixes one of the issues reported by Sashiko during a previous review
of this series, linked here as the Closes tag.
Closes: https://lore.kernel.org/netdev/20260916011228.1632848-1-kuba@kernel.org/
Fixes: 3a7496234d17 ("ice: implement basic E822 PTP support")
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_adapter.c | 3 +
drivers/net/ethernet/intel/ice/ice_adapter.h | 4 ++
drivers/net/ethernet/intel/ice/ice_ptp.c | 58 ++++++++++----------
drivers/net/ethernet/intel/ice/ice_ptp.h | 2 -
4 files changed, 36 insertions(+), 31 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.c b/drivers/net/ethernet/intel/ice/ice_adapter.c
index 536923b6ae97..572862fcd247 100644
--- a/drivers/net/ethernet/intel/ice/ice_adapter.c
+++ b/drivers/net/ethernet/intel/ice/ice_adapter.c
@@ -77,6 +77,8 @@ static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev)
spin_lock_init(&adapter->ports.lock);
INIT_LIST_HEAD(&adapter->ports.list);
+ mutex_init(&adapter->ps_lock);
+
return adapter;
}
@@ -87,6 +89,7 @@ static void ice_adapter_free(struct ice_adapter *adapter)
mutex_destroy(&adapter->cpi_phy_lock[i]);
cleanup_srcu_struct(&adapter->ports.srcu);
+ mutex_destroy(&adapter->ps_lock);
kfree(adapter);
}
diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.h b/drivers/net/ethernet/intel/ice/ice_adapter.h
index 0b01c7f5cf0d..5f39166795b6 100644
--- a/drivers/net/ethernet/intel/ice/ice_adapter.h
+++ b/drivers/net/ethernet/intel/ice/ice_adapter.h
@@ -40,6 +40,7 @@ struct ice_port_list {
* @txq_ctx_lock: Spinlock protecting access to the GLCOMM_QTX_CNTX_CTL register
* @cpi_phy_lock: Per-PHY mutex serializing CPI REQ/ACK transactions.
* Index 0 = PHY0, index 1 = PHY1. Used on E825C devices.
+ * @ps_lock: Mutex to serialize PHY port start/stop across adapter.
* @ctrl_pf: Control PF of the adapter
* @ports: Ports list
* @index: 64-bit index cached for collision detection on 32bit systems
@@ -53,6 +54,9 @@ struct ice_adapter {
/* Serialize CPI REQ/ACK transactions per PHY (E825C only) */
struct mutex cpi_phy_lock[ICE_E825_MAX_PHYS];
+ /* For serializing PHY port start/stop sequences */
+ struct mutex ps_lock;
+
struct ice_pf *ctrl_pf;
struct ice_port_list ports;
u64 index;
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 94a66e9d8c05..adc5308baefc 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -1191,6 +1191,8 @@ static void ice_ptp_wait_for_offsets(struct kthread_work *work)
/**
* ice_ptp_port_phy_stop - Stop timestamping for a PHY port
* @ptp_port: PTP port to stop
+ *
+ * Context: must hold the adapter ps_lock.
*/
static int
ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)
@@ -1200,7 +1202,7 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)
struct ice_hw *hw = &pf->hw;
int err;
- mutex_lock(&ptp_port->ps_lock);
+ lockdep_assert_held(&pf->adapter->ps_lock);
switch (hw->mac_type) {
case ICE_MAC_E810:
@@ -1222,8 +1224,6 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)
dev_err(ice_pf_to_dev(pf), "PTP failed to set PHY port %d down, err %d\n",
port, err);
- mutex_unlock(&ptp_port->ps_lock);
-
return err;
}
@@ -1234,6 +1234,8 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)
* Start the PHY timestamping block, and initiate Vernier timestamping
* calibration. If timestamping cannot be calibrated (such as if link is down)
* then disable the timestamping block instead.
+ *
+ * Context: must hold the adapter ps_lock.
*/
static int
ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)
@@ -1244,11 +1246,11 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)
unsigned long flags;
int err;
+ lockdep_assert_held(&pf->adapter->ps_lock);
+
if (!ptp_port->link_up)
return ice_ptp_port_phy_stop(ptp_port);
- mutex_lock(&ptp_port->ps_lock);
-
switch (hw->mac_type) {
case ICE_MAC_E810:
case ICE_MAC_E830:
@@ -1290,8 +1292,6 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)
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);
-
return err;
}
@@ -1310,12 +1310,13 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
ptp_port = &pf->ptp.port;
- /* Update cached link status for this port immediately */
+ mutex_lock(&pf->adapter->ps_lock);
+
ptp_port->link_up = linkup;
/* Skip HW writes if reset is in progress */
if (pf->hw.reset_ongoing)
- return;
+ goto out_unlock;
if (hw->mac_type == ICE_MAC_GENERIC_3K_E825 &&
test_bit(ICE_FLAG_DPLL, pf->flags)) {
@@ -1358,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;
+ break;
case ICE_MAC_GENERIC:
ice_ptp_port_phy_restart(ptp_port);
- return;
+ break;
case ICE_MAC_GENERIC_3K_E825:
if (linkup)
ice_ptp_port_phy_restart(ptp_port);
- return;
+ break;
default:
dev_warn(ice_pf_to_dev(pf), "%s: Unknown PHY type\n", __func__);
}
+
+out_unlock:
+ mutex_unlock(&pf->adapter->ps_lock);
}
/**
@@ -1434,18 +1438,11 @@ static int ice_ptp_cfg_phy_interrupt(struct ice_pf *pf, bool ena, u32 threshold)
}
}
-/**
- * ice_ptp_reset_phy_timestamping - Reset PHY timestamping block
- * @pf: Board private structure
- */
-static void ice_ptp_reset_phy_timestamping(struct ice_pf *pf)
-{
- ice_ptp_port_phy_restart(&pf->ptp.port);
-}
-
/**
* ice_ptp_restart_all_phy - Restart all PHYs to recalibrate timestamping
* @pf: Board private structure
+ *
+ * Context: acquires the adapter ps_lock
*/
static void ice_ptp_restart_all_phy(struct ice_pf *pf)
{
@@ -1453,6 +1450,8 @@ static void ice_ptp_restart_all_phy(struct ice_pf *pf)
struct ice_ptp_port *port;
int srcu_idx;
+ mutex_lock(&pf->adapter->ps_lock);
+
srcu_idx = srcu_read_lock(&ports->srcu);
list_for_each_entry_srcu(port, &ports->list, list_node,
srcu_read_lock_held(&ports->srcu)) {
@@ -1465,6 +1464,8 @@ static void ice_ptp_restart_all_phy(struct ice_pf *pf)
kref_put(&port->ref, ice_ptp_release_port_srcu);
}
srcu_read_unlock(&ports->srcu, srcu_idx);
+
+ mutex_unlock(&pf->adapter->ps_lock);
}
/**
@@ -3303,8 +3304,6 @@ static int ice_ptp_init_port(struct ice_pf *pf, struct ice_ptp_port *ptp_port)
{
struct ice_hw *hw = &pf->hw;
- mutex_init(&ptp_port->ps_lock);
-
switch (hw->mac_type) {
case ICE_MAC_E810:
case ICE_MAC_E830:
@@ -3404,14 +3403,16 @@ void ice_ptp_init(struct ice_pf *pf)
err = ice_ptp_init_port(pf, &ptp->port);
if (err)
- goto err_destroy_ps_lock;
+ goto err_exit;
err = ice_ptp_setup_pf(pf);
if (err)
goto err_release_tx_tracker;
/* Start the PHY timestamping block */
- ice_ptp_reset_phy_timestamping(pf);
+ mutex_lock(&pf->adapter->ps_lock);
+ ice_ptp_port_phy_restart(&ptp->port);
+ mutex_unlock(&pf->adapter->ps_lock);
/* Configure initial Tx interrupt settings */
ice_ptp_cfg_tx_interrupt(pf);
@@ -3429,8 +3430,6 @@ void ice_ptp_init(struct ice_pf *pf)
ice_ptp_cleanup_pf(pf);
err_release_tx_tracker:
ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
-err_destroy_ps_lock:
- mutex_destroy(&ptp->port.ps_lock);
err_exit:
/* If we registered a PTP clock, release it */
if (pf->ptp.clock) {
@@ -3470,7 +3469,6 @@ void ice_ptp_release(struct ice_pf *pf)
}
ice_ptp_cleanup_pf(pf);
ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
- mutex_destroy(&pf->ptp.port.ps_lock);
return;
}
@@ -3487,8 +3485,10 @@ void ice_ptp_release(struct ice_pf *pf)
kthread_cancel_delayed_work_sync(&pf->ptp.work);
+ mutex_lock(&pf->adapter->ps_lock);
ice_ptp_port_phy_stop(&pf->ptp.port);
- mutex_destroy(&pf->ptp.port.ps_lock);
+ mutex_unlock(&pf->adapter->ps_lock);
+
if (pf->ptp.kworker) {
kthread_destroy_worker(pf->ptp.kworker);
pf->ptp.kworker = NULL;
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h
index da2003ba3bb0..27ea502b7576 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.h
@@ -143,7 +143,6 @@ struct ice_ptp_tx {
* @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
* @link_up: indicates whether the link is up
* @tx_fifo_busy_cnt: number of times the Tx FIFO was busy
* @port_num: the port number this structure represents
@@ -155,7 +154,6 @@ struct ice_ptp_port {
struct kref ref;
struct ice_ptp_tx tx;
struct kthread_delayed_work ov_work;
- struct mutex ps_lock; /* protects overall PTP PHY start procedure */
bool link_up;
u8 tx_fifo_busy_cnt;
u8 port_num;
--
2.47.1
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH net v2 03/15] ice: fix PHY port restart serialization
2026-10-08 21:56 ` [PATCH net v2 03/15] ice: fix PHY port restart serialization Tony Nguyen
@ 2026-10-12 23:15 ` netdev-bot+sashiko
0 siblings, 0 replies; 28+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 23:15 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
maciej.machnikowski, jacob.e.keller, przemyslaw.korba,
grzegorz.nitka, sergey.temerkhanov, arkadiusz.kubalewski, poros,
richardcochran, horms, pmenzel, aleksandr.loktionov,
alexander.nowlin
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 1 (2) · Medium: 0 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] net: ice: UAF/NULL deref of PTP kworker on error path during PHY
restart
Pre-existing issues:
- [High] On E822 (ICE_MAC_GENERIC), ice_ptp_port_phy_restart() can call
kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work, 0)…
- [High] ice_ptp_link_change() checks `pf->ptp.state != ICE_PTP_READY`
(line 1308) before it takes adapter->ps_lock (line 1313), and does not…
--
ice: fix PHY port restart serialization
This commit replaces the per-port ps_lock with one mutex for the whole
adapter. The mutex is taken in ice_ptp_restart_all_phy(),
ice_ptp_link_change(), ice_ptp_init() and ice_ptp_release(), and the
stop/restart helpers now assert that it is held. The cached link_up field
is now written in the same critical section as the PHY restart.
Closes: https://lore.kernel.org/netdev/20260916011228.1632848-1-kuba@kernel.org/
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
> index 94a66e9d8c05d..adc5308baefce 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
[ ... ]
> @@ -1310,12 +1310,13 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
>
> ptp_port = &pf->ptp.port;
>
> - /* Update cached link status for this port immediately */
> + mutex_lock(&pf->adapter->ps_lock);
> +
> ptp_port->link_up = linkup;
[Severity: High]
This is a pre-existing issue, but now that ps_lock is taken here, should
ice_ptp_link_change() check ICE_PTP_READY again after mutex_lock()?
Right now the state is only checked before the lock is taken:
if (pf->ptp.state != ICE_PTP_READY)
return;
ptp_port = &pf->ptp.port;
mutex_lock(&pf->adapter->ps_lock);
ice_ptp_release() does three things in order. It sets the state to
ICE_PTP_UNINIT, it takes and drops ps_lock around ice_ptp_port_phy_stop(),
and then it destroys the worker:
mutex_lock(&pf->adapter->ps_lock);
ice_ptp_port_phy_stop(&pf->ptp.port);
mutex_unlock(&pf->adapter->ps_lock);
if (pf->ptp.kworker) {
kthread_destroy_worker(pf->ptp.kworker);
pf->ptp.kworker = NULL;
}
Suppose a link change passes the READY check and then blocks on ps_lock,
for example behind a sibling PF's restart holding the adapter-wide mutex.
If release takes the lock first, the link change then runs against the
torn-down port:
- E82x with linkup == false: ice_ptp_port_phy_stop() calls
kthread_cancel_delayed_work_sync(&ptp_port->ov_work), but
ov_work.work->worker still points at the freed kthread_worker.
- linkup == true: ice_ptp_port_phy_restart() calls
kthread_queue_delayed_work() with pf->ptp.kworker == NULL.
Two callers can race with ice_unload()->ice_deinit_features()->
ice_ptp_release(). One is ice_down() (ndo_stop under rtnl). The other is
ice_link_event(), called from the service task.
The service task keeps running through ice_unload(), because
ice_service_task_stop() is only called later, from ice_deinit_dev().
Later in the series the ice_down() caller goes away. ice_link_event() and
ice_rebuild() still call ice_ptp_link_change() from the service task,
though, and the state check is still outside the lock at the end of the
series. The same window existed with the old per-port lock.
[ ... ]
> @@ -3404,14 +3403,16 @@ void ice_ptp_init(struct ice_pf *pf)
>
> err = ice_ptp_init_port(pf, &ptp->port);
> if (err)
> - goto err_destroy_ps_lock;
> + goto err_exit;
>
> err = ice_ptp_setup_pf(pf);
> if (err)
> goto err_release_tx_tracker;
>
> /* Start the PHY timestamping block */
> - ice_ptp_reset_phy_timestamping(pf);
> + mutex_lock(&pf->adapter->ps_lock);
> + ice_ptp_port_phy_restart(&ptp->port);
> + mutex_unlock(&pf->adapter->ps_lock);
[Severity: High]
This isn't a bug introduced by this patch, but on E822, can the
ice_ptp_port_phy_restart() call here dereference a NULL pf->ptp.kworker?
On the ICE_MAC_GENERIC success path, the restart queues work without
checking the worker:
kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work,
0);
ice_ptp_init_work() only runs later in ice_ptp_init(). That is after this
restart, and after ptp->state is set to ICE_PTP_READY.
ice_ptp_release() destroys the worker and sets pf->ptp.kworker = NULL, but
it never clears ptp.port.link_up. Link events after release return early
because the state is not READY.
So take a devlink reload (ice_unload() -> ice_load()) while the link is
up. link_up is still true when this restart runs, and
kthread_queue_delayed_work() then takes worker->lock on a NULL worker
while ps_lock is held.
Two other paths reach the same call:
- A sibling PF's ice_ptp_restart_all_phy() (from owner settime64 or
rebuild) can find this port as soon as ice_ptp_setup_pf() has added it
to the adapter ports list with list_add_rcu().
- A link-up event can see ICE_PTP_READY before the worker exists.
The later patch "ice: call PTP link change only from link events" moves
ice_ptp_init_work() ahead of the restart and the READY transition, and
seeds link_up. That covers this init-path restart and the link-up event.
The sibling PF path still looks open at the end of the series.
ice_ptp_setup_pf() still publishes the port on the list before the worker
is created and before link_up is reseeded. ice_ptp_restart_all_phy() also
does not check ptp.state.
Would creating the worker, or clearing link_up, before the port is added
to the ports list close this window?
[ ... ]
> @@ -3470,7 +3469,6 @@ void ice_ptp_release(struct ice_pf *pf)
> }
> ice_ptp_cleanup_pf(pf);
> ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
> - mutex_destroy(&pf->ptp.port.ps_lock);
> return;
> }
>
[Severity: High]
This hunk only drops the mutex_destroy(). Looking at the rest of this
branch, though, can a sibling PF still reach this port after its
kworker has been destroyed?
In the ice_ptp_release() branch for a PF whose state is not
ICE_PTP_READY, the order is:
- kthread_destroy_worker() is called and pf->ptp.kworker is set to
NULL.
- ice_ptp_cleanup_pf() is called after that.
ice_ptp_cleanup_pf() is what removes the port from adapter->ports.list.
Until it runs, the port is still published on the list.
During that window, another PF can call ice_ptp_restart_all_phy(), for
example from ice_ptp_settime64() on the clock owner or from rebuild.
That function:
- walks the list under SRCU,
- takes a port->ref,
- calls ice_ptp_port_phy_restart() without looking at ptp.state.
Nothing in release clears ptp_port->link_up, so it can still be true.
On ICE_MAC_GENERIC the restart then ends in:
kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work,
0);
Depending on timing, pf->ptp.kworker is either already freed or NULL.
If link_up is false, the walker goes through ice_ptp_port_phy_stop()
instead. Its kthread_cancel_delayed_work_sync(&ptp_port->ov_work) may
also end up taking the lock of the freed worker that
ov_work.work.worker still points to.
This matters for this patch because ice_ptp_restart_all_phy() now
holds adapter->ps_lock across the walk, and ps_lock is meant to be
what serializes PHY start/stop. This branch of ice_ptp_release() never
takes ps_lock, so the new lock does not order the teardown against a
concurrent restart from a sibling PF. As I read it, the READY branch
unpublishes the port before the worker goes away, so it does not have
this window.
Should ice_ptp_cleanup_pf() run before the worker is destroyed here,
as in the READY path? The idea is that the port is unpublished, and
any SRCU walker that already found it has finished, before
pf->ptp.kworker is freed. Or would you rather take ps_lock around this
teardown?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008215614.1987250-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH net v2 04/15] ice: set in_use only after preparing Tx timestamp index
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
` (2 preceding siblings ...)
2026-10-08 21:56 ` [PATCH net v2 03/15] ice: fix PHY port restart serialization Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-08 21:56 ` [PATCH net v2 05/15] ice: call PTP link change only from link events Tony Nguyen
` (10 subsequent siblings)
14 siblings, 0 replies; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Jacob Keller, anthony.l.nguyen, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, poros, richardcochran, horms,
Alexander Nowlin
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 <alexander.nowlin@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index adc5308baefc..bbb57edcec0f 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -592,6 +592,9 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
bool drop_ts = !link_up;
struct sk_buff *skb;
+ /* Prevent speculative re-ordering of start and skb */
+ smp_rmb();
+
/* Drop packets which have waited for more than 2 seconds */
if (time_is_before_jiffies(tx->tstamps[idx].start + 2 * HZ)) {
drop_ts = true;
@@ -2670,11 +2673,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
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH net v2 05/15] ice: call PTP link change only from link events
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
` (3 preceding siblings ...)
2026-10-08 21:56 ` [PATCH net v2 04/15] ice: set in_use only after preparing Tx timestamp index Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-12 23:15 ` netdev-bot+sashiko
2026-10-08 21:56 ` [PATCH net v2 06/15] ice: E822: keep Tx timestamps disabled during offset calibration Tony Nguyen
` (9 subsequent siblings)
14 siblings, 1 reply; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Arkadiusz Kubalewski, anthony.l.nguyen, jacob.e.keller,
maciej.machnikowski, przemyslaw.korba, grzegorz.nitka,
sergey.temerkhanov, poros, richardcochran, horms,
Aleksandr Loktionov, Alexander Nowlin
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(), resulting in four 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.
4. Administrative commands such as changing MTU trigger ice_down_up() which
then forced a PHY restart that is destructive to outstanding timestamp
processing that is otherwise unaffected by the administrative actions.
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 <aleksandr.loktionov@intel.com>
Signed-off-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Signed-off-by: Przemyslaw Korba <przemyslaw.korba@intel.com>
Signed-off-by: Petr Oros <poros@redhat.com>
Reviewed-by: Maciek Machnikowski <maciej.machnikowski@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
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 | 41 ++++++++++++++++-------
2 files changed, 37 insertions(+), 14 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 bbb57edcec0f..220d174397ba 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -583,7 +583,7 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
}
/* Drop packets if the link went down */
- link_up = ptp_port->link_up;
+ link_up = READ_ONCE(ptp_port->link_up);
for_each_set_bit(idx, tx->in_use, tx->len) {
struct skb_shared_hwtstamps shhwtstamps = {};
@@ -1251,7 +1251,7 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)
lockdep_assert_held(&pf->adapter->ps_lock);
- if (!ptp_port->link_up)
+ if (!READ_ONCE(ptp_port->link_up))
return ice_ptp_port_phy_stop(ptp_port);
switch (hw->mac_type) {
@@ -1315,7 +1315,7 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
mutex_lock(&pf->adapter->ps_lock);
- ptp_port->link_up = linkup;
+ WRITE_ONCE(ptp_port->link_up, linkup);
/* Skip HW writes if reset is in progress */
if (pf->hw.reset_ongoing)
@@ -1461,7 +1461,7 @@ static void ice_ptp_restart_all_phy(struct ice_pf *pf)
if (!kref_get_unless_zero(&port->ref))
continue;
- if (port->link_up)
+ if (READ_ONCE(port->link_up))
ice_ptp_port_phy_restart(port);
kref_put(&port->ref, ice_ptp_release_port_srcu);
@@ -3271,9 +3271,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)
{
@@ -3292,9 +3296,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;
}
@@ -3414,8 +3415,23 @@ void ice_ptp_init(struct ice_pf *pf)
if (err)
goto err_release_tx_tracker;
- /* Start the PHY timestamping block */
+ /* Create the kworker before restarting the PHY, which queues work on
+ * it in the E82x restart path.
+ */
+ err = ice_ptp_init_work(pf, ptp);
+ 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.
+ */
mutex_lock(&pf->adapter->ps_lock);
+ if (pf->hw.port_info)
+ WRITE_ONCE(ptp->port.link_up,
+ !!(pf->hw.port_info->phy.link_info.link_info &
+ ICE_AQ_LINK_UP));
+
+ /* Start the PHY timestamping block */
ice_ptp_port_phy_restart(&ptp->port);
mutex_unlock(&pf->adapter->ps_lock);
@@ -3424,9 +3440,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_clean_pf;
+ /* 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
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH net v2 05/15] ice: call PTP link change only from link events
2026-10-08 21:56 ` [PATCH net v2 05/15] ice: call PTP link change only from link events Tony Nguyen
@ 2026-10-12 23:15 ` netdev-bot+sashiko
0 siblings, 0 replies; 28+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 23:15 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
maciej.machnikowski, jacob.e.keller, przemyslaw.korba,
grzegorz.nitka, sergey.temerkhanov, arkadiusz.kubalewski, poros,
richardcochran, horms, pmenzel, aleksandr.loktionov,
alexander.nowlin
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.
Critical: 0 · High: 1 (1) · Medium: 4 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] In the non-READY branch of ice_ptp_release() (state ERROR after
a failed ice_ptp_rebuild_owner(), or RESETTING after a rebuild that…
- [Medium] On E82x, every non-PFR reset (CORER/GLOBR/EMPR) now restarts
the PHY timestamp block twice.
- [Medium] A link event that arrives during the INITIALIZING window of
ice_ptp_init() is now lost for good.
- [Medium] Now that the ice_up_complete() call is gone, edge detection in
ice_link_event() is the only thing that updates PTP link state at…
- [Medium] The new block at the end of ice_rebuild() programs the PTP PHY
from pf->hw.port_info->phy.link_info.link_info without asking firmware…
Pre-existing issues:
- [High] ice_ptp_init() publishes the port on adapter->ports.list
(ice_ptp_setup_pf() -> list_add_rcu) before ice_ptp_init_work()…
--
ice: call PTP link change only from link events
This patch removes the ice_ptp_link_change() calls from ice_up_complete()
and ice_down(), so the link event handler is the only runtime source of
PTP link state. It seeds ptp->port.link_up in ice_ptp_init() and adds an
ice_ptp_link_change() call at the end of ice_rebuild(). It also creates
the kworker before the first PHY restart in ice_ptp_init().
> 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
> @@ -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);
> }
[Severity: Medium]
With this call gone, can PTP miss a link-up that some code other than the
LSE handler notices first?
ice_link_event() only notifies PTP on an edge, and it compares against the
cached pi->phy.link_info:
ice_link_event() {
...
if (link_up == old_link && link_speed == old_link_speed)
return 0;
...
ice_ptp_link_change(pf, link_up);
}
Other paths refresh that cache with Get Link Status:
ice_open_internal() -> ice_update_link_info(), ice_check_media_subtask()
while ICE_FLAG_NO_MEDIA is set, and ethtool ice_set_link_ksettings().
Here is one sequence:
- The netdev is down and the cable is pulled. PTP link_up becomes false,
and on E82x ice_ptp_port_phy_stop() stops the PHY timer.
- The cable is plugged back in.
- 'ip link set up' runs before the service task drains the ARQ.
ice_open_internal() then refreshes the cache to up, and ice_up_complete()
turns the carrier on without telling PTP. When the queued LSE is handled,
it sees old == new and returns early.
Would ptp_port->link_up then stay false while carrier is on, until the next
physical link transition? The E82x PHY timer would stay stopped, and
ice_ptp_process_tx_tstamp() would drop every Tx timestamp. Later in the
series, ice_ptp_is_tx_tracker_up() rejects new requests instead. Before
this patch, the open path handled this case.
> @@ -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);
> }
[Severity: High]
ice_down() no longer clears link_up during reset preparation, so it stays
true through a failed reset. Can ice_ptp_restart_all_phy() then reach a
port whose kworker has already been destroyed?
ice_ptp_release() has a non-READY branch. It runs in state ERROR after a
failed ice_ptp_rebuild_owner(), or in state RESETTING after a rebuild that
failed early. That branch destroys the worker before it removes the port
from adapter->ports.list:
ice_ptp_release() {
...
kthread_destroy_worker(pf->ptp.kworker);
pf->ptp.kworker = NULL;
...
ice_ptp_cleanup_pf(pf);
}
ice_ptp_settime64() does not check the PTP state, and on E82x it calls
ice_ptp_restart_all_phy(). If that runs during this window, it takes a
reference on the port, which is still on the list. With link_up true it
then goes down this path:
ice_ptp_settime64()
ice_ptp_restart_all_phy()
ice_ptp_port_phy_restart()
kthread_cancel_delayed_work_sync(&ptp_port->ov_work)
kthread_queue_delayed_work(pf->ptp.kworker, ...)
The cancel goes through work->worker, which can point at the freed worker.
The queue call passes a NULL worker.
Before this patch, ice_down() cleared link_up from ice_prepare_for_reset(),
so ice_ptp_restart_all_phy() skipped the port.
The order of destroying the worker before removing the port comes from the
earlier patch in this series, "ice: fix removal of PTP timestamp tracker
during reset". This change makes it reachable for ports that had a running
netdev. Can this end in a use-after-free or a NULL dereference in
kthread_queue_delayed_work()?
> @@ -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: Medium]
Does this restart the E82x PHY timestamp block a second time on every
CORER, GLOBR or EMPR?
Before this patch, ice_prepare_for_reset() -> ice_pf_dis_all_vsi() ->
ice_vsi_close() -> ice_down() cleared ptp_port->link_up while the state was
still READY. ice_ptp_restart_all_phy() then skipped the port, and the one
restart came later, from ice_up_complete().
Now link_up keeps its pre-reset value, so the earlier ice_ptp_rebuild()
call already restarts the port:
ice_rebuild()
ice_ptp_rebuild()
ice_ptp_rebuild_owner()
ice_ptp_restart_all_phy()
if (READ_ONCE(port->link_up))
ice_ptp_port_phy_restart(port);
That cancels ov_work, sets calibrating, starts the PHY timer and queues
ov_work. Then this new block calls ice_ptp_link_change().
hw.reset_ongoing was already cleared in ice_reset_subtask() before
ice_rebuild() ran, so the HW writes go ahead. On ICE_MAC_GENERIC this calls
ice_ptp_port_phy_restart() again every time. On E825 it does so when
linkup is true:
ice_ptp_link_change() {
...
case ICE_MAC_GENERIC:
ice_ptp_port_phy_restart(ptp_port);
break;
...
}
So the calibration that was just started is thrown away and started again.
The owner's ice_ptp_restart_all_phy() also restarts the ports of other PFs.
Each of those PFs then restarts its own port again at the end of its own
rebuild.
The commit message names this as the first problem being fixed:
1. Double initialization on link-up: ice_handle_link_event() calls
ice_ptp_link_change(true), then ice_up_complete() calls it again.
Does the reset path now get the same kind of double restart?
[Severity: Medium]
Is link_info current at this point after the reset?
ice_update_pf_netdev_link(), just above, uses ice_get_link_status(). That
only sends Get Link Status when phy.get_link_info is set, and in the driver
only ethtool sets that flag:
ice_get_link_status() {
...
if (phy_info->get_link_info) {
...
}
During rebuild, the cache is refreshed only when the netdev is running
(ice_ena_vsi() -> ice_open_internal() -> ice_update_link_info()).
ice_init_all_ctrlq() also drops any LSE that was queued before the reset.
So for a netdev that is down, this seeds PTP from the pre-reset snapshot.
If the reset changed the real link state, the E82x/E825 PHY timer can be
set up for the wrong state.
ice_up_complete() no longer notifies PTP. A later open refreshes the cache
without telling PTP, and the next LSE may then match the cache and be
ignored.
The commit message says:
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.
Should this re-read link_info from firmware before using it? As written, it
restores a cached software value rather than the hardware link status.
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
> index bbb57edcec0f5..220d174397bab 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
[ ... ]
> @@ -3414,8 +3415,23 @@ void ice_ptp_init(struct ice_pf *pf)
> if (err)
> goto err_release_tx_tracker;
>
> - /* Start the PHY timestamping block */
> + /* Create the kworker before restarting the PHY, which queues work on
> + * it in the E82x restart path.
> + */
> + err = ice_ptp_init_work(pf, ptp);
> + if (err)
> + goto err_clean_pf;
> +
[Severity: High]
This is a pre-existing issue, but this reorder doesn't fully close the
window it is aimed at. Can another PF still restart this port before
ptp->kworker is assigned?
ice_ptp_setup_pf() adds the port to adapter->ports.list with
list_add_rcu() before ice_ptp_init_work() runs. The kworker is then
assigned without ps_lock.
ice_ptp_release() sets pf->ptp.kworker = NULL but never clears
pf->ptp.port.link_up. ice_unload() also runs ice_deinit_features() before
ice_unregister_netdev(), so ice_down() never clears it either. After a
devlink reload, the PF therefore re-enters ice_ptp_init() with link_up
still true.
The window includes kthread_run_worker() creating a thread. If the E82x
clock owner handles clock_settime during it:
ice_ptp_settime64()
ice_ptp_restart_all_phy()
kref_get_unless_zero(&port->ref) /* succeeds, port is listed */
if (READ_ONCE(port->link_up)) /* stale true */
ice_ptp_port_phy_restart(port)
kthread_queue_delayed_work(pf->ptp.kworker /* NULL */, ...)
then kthread_queue_delayed_work() dereferences a NULL worker. The window
was larger before this patch.
Would creating the kworker before ice_ptp_setup_pf(), or clearing link_up
in ice_ptp_release(), close it?
> + /* 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.
> + */
> mutex_lock(&pf->adapter->ps_lock);
> + if (pf->hw.port_info)
> + WRITE_ONCE(ptp->port.link_up,
> + !!(pf->hw.port_info->phy.link_info.link_info &
> + ICE_AQ_LINK_UP));
> +
> + /* Start the PHY timestamping block */
> ice_ptp_port_phy_restart(&ptp->port);
> mutex_unlock(&pf->adapter->ps_lock);
>
> @@ -3424,9 +3440,10 @@ void ice_ptp_init(struct ice_pf *pf)
>
> ptp->state = ICE_PTP_READY;
>
[Severity: Medium]
What happens to a link event that is handled after link_up is seeded above
but before ptp->state is set to ICE_PTP_READY here?
The service task is already running at this point. ice_init() clears
ICE_SERVICE_DIS and arms serv_tmr, and ice_init_link() has already called
ice_phy_cfg(). The window covers ice_ptp_port_phy_restart() and
ice_ptp_cfg_tx_interrupt().
If an LSE is handled in that window, ice_link_event() updates the cached
link_info and calls ice_ptp_link_change(pf, true). That call returns
before it records the new state:
ice_ptp_link_change() {
if (pf->ptp.state != ICE_PTP_READY)
return;
...
WRITE_ONCE(ptp_port->link_up, linkup);
...
}
Later LSEs then see link_up == old_link in ice_link_event() and return
early. Before this patch, the ice_up_complete() call fixed this on the next
open, but that call is now removed.
Would ptp_port->link_up stay false while the link is up, until the link
flaps or a reset runs? The E82x/E825 PHY timer would never be started, and
Tx timestamps would be dropped at this commit. Later in the series,
ice_ptp_is_tx_tracker_up() rejects new requests instead.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008215614.1987250-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH net v2 06/15] ice: E822: keep Tx timestamps disabled during offset calibration
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
` (4 preceding siblings ...)
2026-10-08 21:56 ` [PATCH net v2 05/15] ice: call PTP link change only from link events Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-08 21:56 ` [PATCH net v2 07/15] ice: E822: cancel offset verification work during reset preparation Tony Nguyen
` (8 subsequent siblings)
14 siblings, 0 replies; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Karol Kolacinski, jacob.e.keller, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, poros, richardcochran, horms,
Aleksandr Loktionov, Alexander Nowlin
From: Karol Kolacinski <karol.kolacinski@intel.com>
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 the Tx PHY offset is 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
should have their valid bit cleared, but it is better to prevent requests
until the driver has confirmed calibration is complete.
Move the tx.calibrating = false to ice_ptp_wait_for_offsets(), after
Tx offset configuration has completed successfully. This ensures that new
Tx timestamp requests are only accepted after the PHY has properly
calibrated the Tx offset.
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.
Note that sashiko review has previously complained that leaving the
calibrating flag enabled "permanently" disables Tx timestamps if vernier
calibration never completes. This is true, but its important to realize
that timestamps would still fail regardless of whether the flag is set.
Until vernier calibration completes the device will not report valid
timestamps regardless. Thus, keeping the calibrating flag set simply
prevents new timestamp requests from software while it is known that the
hardware will not complete them.
Fixes: 3a7496234d17 ("ice: implement basic E822 PTP support")
Signed-off-by: Karol Kolacinski <karol.kolacinski@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Signed-off-by: Przemyslaw Korba <przemyslaw.korba@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp.c | 29 +++++++++++++++++++-----
drivers/net/ethernet/intel/ice/ice_ptp.h | 2 +-
2 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 220d174397ba..7f82439c47ee 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -1161,6 +1161,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;
@@ -1181,9 +1182,26 @@ static void ice_ptp_wait_for_offsets(struct kthread_work *work)
tx_err = ice_ptp_check_tx_fifo(port);
if (!tx_err)
tx_err = ice_phy_cfg_tx_offset_e82x(hw, port->port_num);
+ if (!tx_err) {
+ /* Tx offset has been configured, re-enable Tx timestamps */
+ spin_lock_irqsave(&port->tx.lock, flags);
+ if (port->tx.calibrating) {
+ port->tx.calibrating = false;
+ dev_dbg(ice_pf_to_dev(pf), "PTP Tx offset valid for port %u, Tx timestamps enabled\n",
+ port->port_num);
+ }
+ spin_unlock_irqrestore(&port->tx.lock, flags);
+ }
+
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));
@@ -1276,11 +1294,10 @@ 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 timestamp requests
+ * remain disabled until ice_ptp_wait_for_offsets() has
+ * verified that the Tx offset calibration has completed.
+ */
kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work,
0);
break;
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h
index 27ea502b7576..029ee4612d76 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.h
@@ -107,7 +107,7 @@ enum ice_tx_tstamp_work {
* @len: length of the tstamps and in_use fields.
* @init: if true, the tracker is initialized;
* @calibrating: if true, the PHY is calibrating the Tx offset. During this
- * window, timestamps are temporarily disabled.
+ * window, timestamp requests are disabled.
* @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.
--
2.47.1
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH net v2 07/15] ice: E822: cancel offset verification work during reset preparation
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
` (5 preceding siblings ...)
2026-10-08 21:56 ` [PATCH net v2 06/15] ice: E822: keep Tx timestamps disabled during offset calibration Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-12 23:15 ` netdev-bot+sashiko
2026-10-08 21:56 ` [PATCH net v2 08/15] ice: E825: stop clearing PHY_REG_TX_OFFSET_READY Tony Nguyen
` (7 subsequent siblings)
14 siblings, 1 reply; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Karol Kolacinski, jacob.e.keller, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, poros, richardcochran, horms,
Aleksandr Loktionov, Alexander Nowlin
From: Karol Kolacinski <karol.kolacinski@intel.com>
The offset verification work task for E822 is executed after a PHY restart
to complete vernier calibration. In the event that either Tx or Rx
calibration hasn't finished, it may still be executing when a reset occurs.
A single PF reset does not trigger a PHY restart, but larger resets (CORE,
GLOBAL, EMP) will initiate a PHY restart.
The ice_ptp_wait_for_offsets() function does check if the driver is
resetting, and will reschedule itself. However, it is possible that a given
execution is already past this check. The function may then fail to access
the sideband queue. It treats failures as a reason to re-schedule and check
again later. This re-schedule will then see the updated reset state via
ice_is_reset_in_progress() and will keep re-scheduling until the reset
finishes.
The larger resets will restart the PHY, canceling any outstanding vernier
calibration and forcing a restart. Preemptively cancel the task for non-PF
resets to avoid doing unnecessary checks while resetting. The task is not
canceled for a PF reset, otherwise nothing would restart the task to finish
vernier calibration after the reset.
Fixes: 95af1f1c4c9f ("ice: reschedule ice_ptp_wait_for_offset_valid during reset")
Signed-off-by: Karol Kolacinski <karol.kolacinski@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Signed-off-by: Przemyslaw Korba <przemyslaw.korba@intel.com>
Reviewed-by: Maciek Machnikowski <maciej.machnikowski@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 7f82439c47ee..7cc151b01a13 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -3006,6 +3006,9 @@ void ice_ptp_prepare_for_reset(struct ice_pf *pf, enum ice_reset_req reset_type)
if (reset_type == ICE_RESET_PFR)
return;
+ 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
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH net v2 07/15] ice: E822: cancel offset verification work during reset preparation
2026-10-08 21:56 ` [PATCH net v2 07/15] ice: E822: cancel offset verification work during reset preparation Tony Nguyen
@ 2026-10-12 23:15 ` netdev-bot+sashiko
0 siblings, 0 replies; 28+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 23:15 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
maciej.machnikowski, jacob.e.keller, przemyslaw.korba,
grzegorz.nitka, sergey.temerkhanov, arkadiusz.kubalewski, poros,
richardcochran, horms, pmenzel, aleksandr.loktionov,
alexander.nowlin
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The commit message gives a false reason for leaving PF resets
out of the new cancellation.
--
ice: E822: cancel offset verification work during reset preparation
This patch cancels the E822 offset verification work (ov_work) in
ice_ptp_prepare_for_reset() for CORE, GLOBAL and EMP resets, so the
work does not keep polling while the PHY is restarted. PF resets
return early and skip the cancel. The commit message says this is
because nothing else would restart the task after a PF reset.
> The larger resets will restart the PHY, canceling any outstanding vernier
> calibration and forcing a restart. Preemptively cancel the task for non-PF
> resets to avoid doing unnecessary checks while resetting. The task is not
> canceled for a PF reset, otherwise nothing would restart the task to finish
> vernier calibration after the reset.
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
> index 7f82439c47ee5..7cc151b01a13e 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
> @@ -3006,6 +3006,9 @@ void ice_ptp_prepare_for_reset(struct ice_pf *pf, enum ice_reset_req reset_type)
> if (reset_type == ICE_RESET_PFR)
> return;
>
> + if (hw->mac_type == ICE_MAC_GENERIC)
> + kthread_cancel_delayed_work_sync(&ptp->port.ov_work);
> +
[Severity: Medium]
Is the statement "nothing would restart the task to finish vernier
calibration after the reset" accurate for this tree?
Near the end of ice_rebuild(), ice_ptp_link_change() is called for
every reset type, PF reset included:
drivers/net/ethernet/intel/ice/ice_main.c:ice_rebuild() {
...
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);
}
...
}
ice_misc_intr() sets hw->reset_ongoing only on the OICR path for
CORER/GLOBR/EMPR. For a PF reset, ice_ptp_link_change() therefore does
not skip the hardware writes. On ICE_MAC_GENERIC it reaches
ice_ptp_port_phy_restart(), which cancels ov_work and queues it again:
drivers/net/ethernet/intel/ice/ice_ptp.c:ice_ptp_port_phy_restart() {
...
case ICE_MAC_GENERIC:
/* Start the PHY timer in Vernier mode */
kthread_cancel_delayed_work_sync(&ptp_port->ov_work);
...
kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work,
0);
break;
...
}
When the link is down, the same function calls ice_ptp_port_phy_stop()
instead, which also cancels ov_work.
The call chain for a PF reset looks like this:
ice_do_reset(PFR)->ice_rebuild()->ice_ptp_link_change()
->ice_ptp_port_phy_restart()
If that is right, cancelling ov_work for PF resets would also be safe.
With the early ICE_RESET_PFR return placed before the new cancel, PF
resets still go through the 100 ms reschedule loop in
ice_ptp_wait_for_offsets(), because ICE_PFR_REQ makes
ice_is_reset_in_progress() return true.
Could the cancel be moved above the PFR early return? If not, could the
commit message give the actual reason for exempting PF resets?
> if (ice_pf_src_tmr_owned(pf) && hw->mac_type == ICE_MAC_GENERIC_3K_E825)
> ice_ptp_prepare_rebuild_sec(pf, false, reset_type);
>
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008215614.1987250-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH net v2 08/15] ice: E825: stop clearing PHY_REG_TX_OFFSET_READY
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
` (6 preceding siblings ...)
2026-10-08 21:56 ` [PATCH net v2 07/15] ice: E822: cancel offset verification work during reset preparation Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-12 23:15 ` netdev-bot+sashiko
2026-10-08 21:56 ` [PATCH net v2 09/15] ice: E825: clear PHY_REG_TX_MEMORY_STATUS prior to soft reset Tony Nguyen
` (6 subsequent siblings)
14 siblings, 1 reply; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Jacob Keller, anthony.l.nguyen, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, poros, richardcochran, horms,
Aleksandr Loktionov, Alexander Nowlin
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.
Notice that the soft_reset parameter of ice_stop_phy_timer_eth56g() is
totally unused. It is a relic from a copy-paste of
ice_stop_phy_timer_e82x() that is unnecessary, so remove it.
Now that we do not clear the PHY_REG_TX_OFFSET_READY, new timestamp
requests could happen while the PHY is calibrating. To avoid this, set the
tx.calibrating field of the Tx timestamp tracker when stopping the timer
and clear it when finishing the restart. This ensures that any new requests
will be rejected until the PHY timer calibration has completed. Also call
ice_ptp_mark_tx_tracker_stale() to prevent reporting any previous
outstanding timestamps to the stack.
Unlike E822 devices, set the calibrating flag when "stopping" the PHY and
clear it immediately after the start procedure. The E825 device does not
perform vernier calibration and thus does not need to wait for hardware to
mark the offsets as valid.
In the event that the PHY timer start procedure fails, the device is in an
unknown state and timestamps will not behave properly. As such, and similar
to E822 devices, the calibrating field is not cleared on failure. This
leaves timestamp requests disabled until the next link restart.
Failure in the start flow is unexpected and it is unclear precisely what
state the hardware is left in. Attempting to add a complex retry mechanism
for a rare event is not worthwhile. The port restart procedure can already
be re-initiated by triggering a link reset (i.e. via ethtool).
Instead update the dev_err message at the end of ice_ptp_port_phy_restart.
Clearly indicate that timestamping is disabled, and add a note that a link
toggle might recover the device. For the invalid MAC type path returning
-ENODEV, skip this message and log a dev_dbg that we failed with an unknown
MAC type instead.
Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
Suggested-by: Maciej Machnikowski <maciej.machnikowski@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp.c | 36 ++++++++++++++++++---
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 25 +++++++-------
drivers/net/ethernet/intel/ice/ice_ptp_hw.h | 2 +-
3 files changed, 46 insertions(+), 17 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 7cc151b01a13..818e2e265a7e 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -1221,6 +1221,7 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)
struct ice_pf *pf = ptp_port_to_pf(ptp_port);
u8 port = ptp_port->port_num;
struct ice_hw *hw = &pf->hw;
+ unsigned long flags;
int err;
lockdep_assert_held(&pf->adapter->ps_lock);
@@ -1236,7 +1237,14 @@ ice_ptp_port_phy_stop(struct ice_ptp_port *ptp_port)
err = ice_stop_phy_timer_e82x(hw, port, true);
break;
case ICE_MAC_GENERIC_3K_E825:
- err = ice_stop_phy_timer_eth56g(hw, port, true);
+ /* Disable new Tx timestamp requests */
+ spin_lock_irqsave(&ptp_port->tx.lock, flags);
+ ptp_port->tx.calibrating = true;
+ spin_unlock_irqrestore(&ptp_port->tx.lock, flags);
+
+ ice_ptp_mark_tx_tracker_stale(&ptp_port->tx);
+
+ err = ice_stop_phy_timer_eth56g(hw, port);
break;
default:
err = -ENODEV;
@@ -1302,15 +1310,35 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)
0);
break;
case ICE_MAC_GENERIC_3K_E825:
+ /* ice_ptp_port_phy_stop() may have already disabled
+ * timestamps, but some restarts occur without first stopping
+ * the timer, so we ensure that new requests are disabled
+ * here.
+ */
+ spin_lock_irqsave(&ptp_port->tx.lock, flags);
+ ptp_port->tx.calibrating = true;
+ spin_unlock_irqrestore(&ptp_port->tx.lock, flags);
+
+ ice_ptp_mark_tx_tracker_stale(&ptp_port->tx);
+
err = ice_start_phy_timer_eth56g(hw, port);
+ if (err)
+ break;
+
+ spin_lock_irqsave(&ptp_port->tx.lock, flags);
+ ptp_port->tx.calibrating = false;
+ spin_unlock_irqrestore(&ptp_port->tx.lock, flags);
+
break;
default:
- err = -ENODEV;
+ dev_dbg(ice_pf_to_dev(pf), "PTP failed to restart PHY port %u with unknown MAC type %d\n",
+ port, hw->mac_type);
+ return -ENODEV;
}
if (err)
- dev_err(ice_pf_to_dev(pf), "PTP failed to set PHY port %d up, err %d\n",
- port, err);
+ dev_err(ice_pf_to_dev(pf), "PTP failed to restart PHY port %u on link-up with err %pe; Timestamping remains disabled; A link-toggle may recover.\n",
+ port, ERR_PTR(err));
return err;
}
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 3a41c711e751..07b55fbb88dd 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -2098,32 +2098,33 @@ static int ice_sync_phy_timer_eth56g(struct ice_hw *hw, u8 port)
}
/**
- * ice_stop_phy_timer_eth56g - Stop the PHY clock timer
+ * ice_stop_phy_timer_eth56g - Clear PHY Rx offset ready flag
* @hw: pointer to the HW struct
* @port: the PHY port to stop
- * @soft_reset: if true, hold the SOFT_RESET bit of PHY_REG_PS
*
- * 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.
+ * Disable Rx timestamping by clearing the PHY_REG_RX_OFFSET_READY. This
+ * causes Rx timestamps to be captured with their valid bit clear, ensuring we
+ * discard any timestamp captured while the PHY is being recalibrated.
+ *
+ * Note this does *not* clear PHY_REG_TX_OFFSET_READY. Clearing it would
+ * cause the Tx timestamps to be captured with their valid bit clear.
+ * Unfortunately the captured timestamps still increment the internal counter
+ * and result in off-by-one accounting. Instead, Tx timestamp requests should
+ * be disabled by other means.
*
* Return:
* * %0 - success
* * %other - failed to write to PHY
*/
-int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset)
+int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port)
{
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;
- ice_debug(hw, ICE_DBG_PTP, "Disabled clock on PHY port %u\n", port);
+ ice_debug(hw, ICE_DBG_PTP, "Disabled Rx timestamps on PHY port %u\n", port);
return 0;
}
@@ -2151,7 +2152,7 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port)
tmr_idx = ice_get_ptp_src_clock_index(hw);
- err = ice_stop_phy_timer_eth56g(hw, port, false);
+ err = ice_stop_phy_timer_eth56g(hw, port);
if (err)
return err;
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
index 16b1988e993d..17000df77ce9 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
@@ -378,7 +378,7 @@ int ice_cgu_get_output_pin_state_caps(struct ice_hw *hw, u8 pin_id,
/* ETH56G family functions */
int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status);
-int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset);
+int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port);
int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port);
int ice_phy_cfg_intr_eth56g(struct ice_hw *hw, u8 port, bool ena, u8 threshold);
int ice_phy_cfg_ptp_1step_eth56g(struct ice_hw *hw, u8 port);
--
2.47.1
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH net v2 08/15] ice: E825: stop clearing PHY_REG_TX_OFFSET_READY
2026-10-08 21:56 ` [PATCH net v2 08/15] ice: E825: stop clearing PHY_REG_TX_OFFSET_READY Tony Nguyen
@ 2026-10-12 23:15 ` netdev-bot+sashiko
0 siblings, 0 replies; 28+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 23:15 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
maciej.machnikowski, jacob.e.keller, przemyslaw.korba,
grzegorz.nitka, sergey.temerkhanov, arkadiusz.kubalewski, poros,
richardcochran, horms, pmenzel, aleksandr.loktionov,
alexander.nowlin
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] Concern: the new ice_ptp_mark_tx_tracker_stale() calls in the
E825 branches of ice_ptp_port_phy_restart() (line 1322) and…
- [Low] The new error message in ice_ptp_port_phy_restart()
(drivers/net/ethernet/intel/ice/ice_ptp.c:1340) blames a link-up…
--
ice: E825: stop clearing PHY_REG_TX_OFFSET_READY
On E825, ice_stop_phy_timer_eth56g() no longer clears
PHY_REG_TX_OFFSET_READY, because clearing it leaves the hardware Tx
timestamp counter out of sync. The E825 stop and restart paths now set
tx.calibrating and mark outstanding Tx timestamps stale, then clear
tx.calibrating once the PHY start succeeds. The patch also drops the
unused soft_reset argument and rewords the restart failure message.
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
> index 7cc151b01a13e..818e2e265a7e5 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
[ ... ]
> @@ -1302,15 +1310,35 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)
> 0);
> break;
> case ICE_MAC_GENERIC_3K_E825:
> + /* ice_ptp_port_phy_stop() may have already disabled
> + * timestamps, but some restarts occur without first stopping
> + * the timer, so we ensure that new requests are disabled
> + * here.
> + */
> + spin_lock_irqsave(&ptp_port->tx.lock, flags);
> + ptp_port->tx.calibrating = true;
> + spin_unlock_irqrestore(&ptp_port->tx.lock, flags);
> +
> + ice_ptp_mark_tx_tracker_stale(&ptp_port->tx);
[Severity: High]
Can this call race with ice_ptp_release() freeing the Tx tracker bitmaps?
At this commit, ice_ptp_mark_tx_tracker_stale() does not check tx->init:
ice_ptp_mark_tx_tracker_stale() {
spin_lock_irqsave(&tx->lock, flags);
bitmap_or(tx->stale, tx->stale, tx->in_use, tx->len);
spin_unlock_irqrestore(&tx->lock, flags);
}
ice_ptp_release_tx_tracker() clears tx->init under tx->lock. It then
frees the bitmaps without holding tx->lock or ps_lock:
bitmap_free(tx->in_use);
tx->in_use = NULL;
bitmap_free(tx->stale);
tx->stale = NULL;
tx->len = 0;
ice_ptp_link_change() checks pf->ptp.state against ICE_PTP_READY without
a lock, before it takes ps_lock. ice_ptp_release() sets ICE_PTP_UNINIT
without holding ps_lock.
So on E825, the service task could handle a link-up event during driver
removal and reach this path:
ice_ptp_link_change()
ice_ptp_port_phy_restart()
ice_ptp_mark_tx_tracker_stale()
bitmap_or(tx->stale, tx->stale, tx->in_use, tx->len)
If that happens while ice_ptp_release_tx_tracker() is freeing or clearing
tx->stale and tx->in_use, this would touch freed or NULL bitmaps. The new
call in the E825 branch of ice_ptp_port_phy_stop() has the same
exposure.
The direct call ice_ptp_release() -> ice_ptp_port_phy_stop() is safe,
because it runs after tx->len has already been set to 0.
A later patch in the series, "ice: keep Tx timestamp slots tracked until
completion or timeout", appears to fix this. It wraps the bitmap_or() in
ice_ptp_mark_tx_tracker_stale() with an if (tx->init) check under
tx->lock.
Could that check be added here, or could that patch be ordered before
this one, so the tree stays safe at every commit for bisection?
> +
> err = ice_start_phy_timer_eth56g(hw, port);
> + if (err)
> + break;
> +
> + spin_lock_irqsave(&ptp_port->tx.lock, flags);
> + ptp_port->tx.calibrating = false;
> + spin_unlock_irqrestore(&ptp_port->tx.lock, flags);
> +
> break;
> default:
> - err = -ENODEV;
> + dev_dbg(ice_pf_to_dev(pf), "PTP failed to restart PHY port %u with unknown MAC type %d\n",
> + port, hw->mac_type);
> + return -ENODEV;
> }
>
> if (err)
> - dev_err(ice_pf_to_dev(pf), "PTP failed to set PHY port %d up, err %d\n",
> - port, err);
> + dev_err(ice_pf_to_dev(pf), "PTP failed to restart PHY port %u on link-up with err %pe; Timestamping remains disabled; A link-toggle may recover.\n",
> + port, ERR_PTR(err));
[Severity: Low]
Is "on link-up" accurate for every caller of ice_ptp_port_phy_restart()?
ice_ptp_link_change() calls it, but so does ice_ptp_restart_all_phy(),
which runs from the reset rebuild path and from ice_ptp_settime64():
ice_ptp_restart_all_phy() {
...
if (READ_ONCE(port->link_up))
ice_ptp_port_phy_restart(port);
...
}
ice_ptp_init() also calls it directly:
/* Start the PHY timestamping block */
ice_ptp_port_phy_restart(&ptp->port);
At this commit, the settime64 path only applies to E822. Later in the
series, "ice: Recalibrate PHY after settime64 on E825-C" extends it to
E825 as well.
If the PHY start fails during rebuild, init or settime, this message
says a link-up event caused it. The wording is still the same at the end
of the series.
Would something like "PTP failed to restart PHY port %u" without the "on
link-up" part be more accurate?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008215614.1987250-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH net v2 09/15] ice: E825: clear PHY_REG_TX_MEMORY_STATUS prior to soft reset
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
` (7 preceding siblings ...)
2026-10-08 21:56 ` [PATCH net v2 08/15] ice: E825: stop clearing PHY_REG_TX_OFFSET_READY Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-12 23:15 ` netdev-bot+sashiko
2026-10-08 21:56 ` [PATCH net v2 10/15] ice: E825: perform a soft reset when starting the PHY timer Tony Nguyen
` (5 subsequent siblings)
14 siblings, 1 reply; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Jacob Keller, anthony.l.nguyen, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, poros, richardcochran, horms,
Aleksandr Loktionov, Alexander Nowlin
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.
If a read fails (i.e. due to a transient sideband queue failure) it is not
treated as a fatal error. There is already a dynamic debug message for each
read failure. Keep track of the total number of timestamp registers that
fail for a given port and log a warning message indicating the total number
of failures. Continuing is acceptable as clearing the memory is a
precaution against faulty software. Correct software access won't read the
index twice and won't read it at all except when it has already confirmed
that the index is valid by reading ice_get_phy_tx_tstamp_ready().
Fixes: 3ec46e157c7f ("ice: perform PHY soft reset for E825C ports at initialization")
Reviewed-by: Maciek Machnikowski <maciej.machnikowski@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 106 ++++++++++----------
1 file changed, 55 insertions(+), 51 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 07b55fbb88dd..e83ad2bb8d42 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
@@ -1134,16 +1116,12 @@ static int ice_read_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx,
* @port: the quad to read from
* @idx: the timestamp index to reset
*
- * Read and then forcibly clear the timestamp index to ensure the valid bit is
- * cleared and the timestamp status bit is reset in the PHY port memory of
- * 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().
+ * Read the timestamp index to ensure that the valid bit is cleared and the
+ * timestamp status bit is reset in the PHY port memory.
*
- * This function should only be called on an idx whose bit is set according to
- * ice_get_phy_tx_tstamp_ready().
+ * This function should only be called on an index whose bit is set according
+ * to ice_get_phy_tx_tstamp_ready(), or as part of a full sweep when paired
+ * with a PHY soft reset via ice_ptp_phy_soft_reset_eth56g().
*
* Return:
* * %0 - success
@@ -1152,24 +1130,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 +1147,43 @@ 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
- */
-static void ice_ptp_reset_ts_memory_eth56g(struct ice_hw *hw)
-{
- unsigned int port;
-
- 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);
+ * @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.
+ *
+ * Failure to read a given index (i.e. due to a transient sideband queue
+ * failure) is not considered fatal as the PHY port is about to be soft reset.
+ * While the soft reset does not clear the timestamp memory, software
+ * shouldn't be reading the timestamp memory without already knowing it is
+ * valid via ice_get_phy_tx_tstamp_ready(), and this sweep is just
+ * a precaution to ensure the memory is in a known state.
+ */
+static void ice_ptp_clear_tx_memory_status_eth56g(struct ice_hw *hw, u8 port)
+{
+ int err, failed = 0;
+ u8 idx;
+
+ for (idx = 0; idx < INDEX_PER_PORT; idx++) {
+ err = ice_clear_ptp_tstamp_eth56g(hw, port, idx);
+ if (err)
+ failed++;
}
+
+ if (failed)
+ dev_warn(ice_hw_to_dev(hw), "Failed to clear %d PHY timestamp registers for port %u\n",
+ failed, port);
}
/**
@@ -2295,6 +2289,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
@@ -2303,6 +2298,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.
*/
@@ -2311,6 +2312,8 @@ int ice_ptp_phy_soft_reset_eth56g(struct ice_hw *hw, u8 port)
u32 global_val;
int err;
+ ice_ptp_clear_tx_memory_status_eth56g(hw, port);
+
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",
@@ -5802,8 +5805,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
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH net v2 09/15] ice: E825: clear PHY_REG_TX_MEMORY_STATUS prior to soft reset
2026-10-08 21:56 ` [PATCH net v2 09/15] ice: E825: clear PHY_REG_TX_MEMORY_STATUS prior to soft reset Tony Nguyen
@ 2026-10-12 23:15 ` netdev-bot+sashiko
0 siblings, 0 replies; 28+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 23:15 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
maciej.machnikowski, jacob.e.keller, przemyslaw.korba,
grzegorz.nitka, sergey.temerkhanov, arkadiusz.kubalewski, poros,
richardcochran, horms, pmenzel, aleksandr.loktionov,
alexander.nowlin
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The kernel-doc for ice_clear_ptp_tstamp_eth56g() in
drivers/net/ethernet/intel/ice/ice_ptp_hw.c no longer matches the code.
- [Low] The new kernel-doc for ice_ptp_phy_soft_reset_eth56g()
(ice_ptp_hw.c:2301-2305) says 'The PHY_REG_TX_MEMORY_STATUS reads…
--
ice: E825: clear PHY_REG_TX_MEMORY_STATUS prior to soft reset
This commit removes ice_ptp_reset_ts_memory_eth56g(), which wrote zeros to
PHY_REG_TX_MEMORY_STATUS. It also drops the write from
ice_clear_ptp_tstamp_eth56g(). In its place, every timestamp index of a port
is now read just before the PHY soft reset in
ice_ptp_phy_soft_reset_eth56g().
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> index 07b55fbb88dde..e83ad2bb8d420 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
[ ... ]
> @@ -1134,16 +1116,12 @@ static int ice_read_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx,
> * @port: the quad to read from
> * @idx: the timestamp index to reset
> *
> - * Read and then forcibly clear the timestamp index to ensure the valid bit is
> - * cleared and the timestamp status bit is reset in the PHY port memory of
> - * 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().
> + * Read the timestamp index to ensure that the valid bit is cleared and the
> + * timestamp status bit is reset in the PHY port memory.
> *
> - * This function should only be called on an idx whose bit is set according to
> - * ice_get_phy_tx_tstamp_ready().
> + * This function should only be called on an index whose bit is set according
> + * to ice_get_phy_tx_tstamp_ready(), or as part of a full sweep when paired
> + * with a PHY soft reset via ice_ptp_phy_soft_reset_eth56g().
> *
> * Return:
> * * %0 - success
[Severity: Low]
This isn't a bug, but the kernel-doc for ice_clear_ptp_tstamp_eth56g()
now looks a bit out of date. The Return section just below this hunk still
says:
* * %other - failed to write to PHY
After this patch the function does not write anything. The only error it
returns comes from ice_read_ptp_tstamp_eth56g(). That is either a sideband
read failure or -EINVAL from the port range check in
ice_read_port_eth56g().
Since this block is being rewritten anyway, could the summary line and the
@port line be updated too? They still read:
* ice_clear_ptp_tstamp_eth56g - Clear a timestamp from the quad block
* @port: the quad to read from
On ETH56G the timestamp memory is per port. The new sibling helper uses
"Reset one port's Tx timestamp memory" and "@port: port number to clear".
> @@ -1152,24 +1130,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);
[ ... ]
> @@ -2303,6 +2298,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.
[Severity: Low]
Does this comment name the wrong register? The sweep never reads
PHY_REG_TX_MEMORY_STATUS. The reads go through this path:
ice_ptp_clear_tx_memory_status_eth56g()
ice_clear_ptp_tstamp_eth56g()
ice_read_ptp_tstamp_eth56g()
ice_read_ptp_tstamp_eth56g() reads the per-index timestamp memory words via
the ETH56G_PHY_MEM_PTP resource:
lo_addr = (u16)PHY_TSTAMP_L(idx);
hi_addr = (u16)PHY_TSTAMP_U(idx);
err = ice_read_port_mem_eth56g(hw, port, lo_addr, &lo);
PHY_REG_TX_MEMORY_STATUS_L/U is the ready bitmap in ETH56G_PHY_REG_PTP. It
is only read by ice_get_phy_tx_tstamp_ready_eth56g(), and reading it does
not clear anything.
The commit message has the same wording:
We must read the PHY_REG_TX_MEMORY_STATUS in order to clear stale
timestamps.
and:
The reads to PHY_REG_TX_MEMORY_STATUS will reset the region valid
bits ensuring that no stale data is left behind.
Could the comment and commit message say the timestamp memory entries
(PHY_TSTAMP_L/U) are being read? Otherwise a later change might "fix" the
sweep by reading the status bitmap, which would not clear any entries.
> + *
> * Return: 0 on success, or a negative error code on failure when
> * reading or writing the PHY register.
> */
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008215614.1987250-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH net v2 10/15] ice: E825: perform a soft reset when starting the PHY timer
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
` (8 preceding siblings ...)
2026-10-08 21:56 ` [PATCH net v2 09/15] ice: E825: clear PHY_REG_TX_MEMORY_STATUS prior to soft reset Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-12 23:15 ` netdev-bot+sashiko
2026-10-08 21:56 ` [PATCH net v2 11/15] ice: wait for in-flight Tx timestamps before flushing the tracker Tony Nguyen
` (4 subsequent siblings)
14 siblings, 1 reply; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Jacob Keller, anthony.l.nguyen, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, poros, richardcochran, horms,
Aleksandr Loktionov, Alexander Nowlin
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
including its timestamp counter has been reset.
Note that despite the name, this PHY soft reset only impacts the timer
block of the PHY, and not its regular Tx/Rx functionality. Additionally,
many configuration registers such as the PHY_REG_TS_INT_CONFIG and
PHY_REG_TX_OFFSET_READY are not reset.
Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
Reviewed-by: Maciek Machnikowski <maciej.machnikowski@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@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 e83ad2bb8d42..e5bfede90431 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -2128,9 +2128,11 @@ int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port)
* @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
@@ -2150,6 +2152,10 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port)
if (err)
return err;
+ err = ice_ptp_phy_soft_reset_eth56g(hw, port);
+ if (err)
+ return err;
+
ice_ptp_src_cmd(hw, ICE_PTP_NOP);
err = ice_phy_cfg_parpcs_eth56g(hw, port);
--
2.47.1
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH net v2 10/15] ice: E825: perform a soft reset when starting the PHY timer
2026-10-08 21:56 ` [PATCH net v2 10/15] ice: E825: perform a soft reset when starting the PHY timer Tony Nguyen
@ 2026-10-12 23:15 ` netdev-bot+sashiko
0 siblings, 0 replies; 28+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 23:15 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
maciej.machnikowski, jacob.e.keller, przemyslaw.korba,
grzegorz.nitka, sergey.temerkhanov, arkadiusz.kubalewski, poros,
richardcochran, horms, pmenzel, aleksandr.loktionov,
alexander.nowlin
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 3 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The patch adds a call to ice_ptp_phy_soft_reset_eth56g() in
ice_start_phy_timer_eth56g() (ice_ptp_hw.c:2155), so every runtime…
- [Medium] The patch runs the sweep plus soft reset on the live restart
path (link-up, rebuild, and at the end of the series settime64) but…
- [Medium] The runtime sweep and soft reset added by this patch are not
synchronized with the Tx timestamp consumer.
- [Low] ice_ptp_clear_tx_memory_status_eth56g() returns void.
--
ice: E825: perform a soft reset when starting the PHY timer
This commit makes ice_start_phy_timer_eth56g() call
ice_ptp_phy_soft_reset_eth56g() right after the PHY timer is stopped.
The E825 PHY timer block is now soft reset, and its Tx timestamp memory
swept, before the PHY is reprogrammed. ice_start_phy_timer_eth56g() is
used by the runtime restart paths, so the sweep and reset now also run on
live ports.
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> index e83ad2bb8d420..e5bfede904314 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
[ ... ]
> @@ -2150,6 +2152,10 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port)
> if (err)
> return err;
>
> + err = ice_ptp_phy_soft_reset_eth56g(hw, port);
> + if (err)
> + return err;
> +
[Severity: Medium]
Does this discard Tx timestamps that the software tracker still treats as
outstanding?
ice_ptp_phy_soft_reset_eth56g() starts by calling
ice_ptp_clear_tx_memory_status_eth56g(). That function reads all
INDEX_PER_PORT entries, which clears every valid/ready bit on the port.
On the E825 branch of ice_ptp_port_phy_restart(), the tracker is only
marked stale before this runs:
drivers/net/ethernet/intel/ice/ice_ptp.c:ice_ptp_port_phy_restart() {
...
ice_ptp_mark_tx_tracker_stale(&ptp_port->tx);
err = ice_start_phy_timer_eth56g(hw, port);
...
}
So outstanding entries keep their in_use bit and their skb reference.
Suppose hardware has captured a timestamp, but the IRQ thread has not
consumed it when the sweep reaches that index. Its ready bit is then gone
and will not be set again. ice_ptp_process_tx_tstamp() skips the entry
until the 2 second timeout:
if (tx->has_ready_bitmap &&
!(tstamp_ready & BIT_ULL(phy_idx))) {
if (drop_ts)
goto skip_ts_read;
continue;
}
When the timeout expires, it increments pf->ptp.tx_hwtstamp_timeouts.
Before this change the timestamp memory survived the restart, so the
stale entry would have been read and dropped quietly.
At this commit, ice_ptp_tx_tstamps_pending() also sees in_use as
non-empty. It keeps re-arming PFINT_OICR_TSYN_TX for those 2 seconds.
A later patch in the series stops counting stale entries in IRQ context.
The 2 second slot/skb hold and the timeout count are still there at the
end of the series, though.
The final patch, "ice: Recalibrate PHY after settime64 on E825-C", makes
ice_ptp_settime64() call ice_ptp_restart_all_phy() on live ports without
flushing, which makes this easier to hit. The rebuild path is not
affected, because ice_ptp_rebuild_owner() calls
ice_ptp_flush_all_tx_tracker() first.
Would it make sense to flush the tracker with ice_ptp_flush_tx_tracker()
before the soft reset, instead of only marking it stale? The commit
message also does not mention that in-flight timestamps are discarded on
every restart.
[Severity: Medium]
Is Tx timestamp capture still enabled while this sequence runs?
ice_stop_phy_timer_eth56g() only clears PHY_REG_RX_OFFSET_READY. Its
kernel-doc says PHY_REG_TX_OFFSET_READY is left set, and that "Tx
timestamp requests should be disabled by other means".
In ice_ptp_port_phy_restart(), the only other means are setting
tx.calibrating = true and calling ice_ptp_mark_tx_tracker_stale(). These
block new ice_ptp_request_ts() reservations. Descriptors already posted to
the Tx ring with a timestamp request are still outstanding in hardware.
Inside ice_ptp_phy_soft_reset_eth56g(), the memory sweep happens first.
Then come a PHY_REG_GLOBAL read/write and a sleep, and only after that is
the reset asserted:
ice_ptp_clear_tx_memory_status_eth56g(hw, port);
...
usleep_range(5000, 6000);
global_val |= PHY_REG_GLOBAL_SOFT_RESET_M;
A timestamp might be captured after the sweep has passed its index but
before the assert. The memory entry would then stay valid while the reset
zeroes the internal outstanding-timestamp counter.
Wouldn't that cause the counter/memory mismatch that the comment on
ice_ptp_clear_tx_memory_status_eth56g() warns "may become out of sync and
prevent new timestamp interrupts"?
On the rebuild path, ice_ptp_flush_all_tx_tracker() has already freed the
software index. A new request that reuses the index could then read the
leftover ready entry.
Previously the soft reset only ran at PHC init, with no traffic. Later in
the series, "ice: wait for in-flight Tx timestamps before flushing the
tracker" adds drain logic, but only to ice_ptp_flush_tx_tracker() and not
to this restart path. The window is still there at the end of the series.
[Severity: Low]
What happens if one of the sweep reads fails here?
ice_ptp_clear_tx_memory_status_eth56g() returns void and only warns:
for (idx = 0; idx < INDEX_PER_PORT; idx++) {
err = ice_clear_ptp_tstamp_eth56g(hw, port, idx);
if (err)
failed++;
}
if (failed)
dev_warn(ice_hw_to_dev(hw), "Failed to clear %d PHY timestamp registers for port %u\n",
failed, port);
ice_ptp_phy_soft_reset_eth56g() then toggles the soft reset anyway. Next,
ice_start_phy_timer_eth56g() sets TX/RX_OFFSET_READY and returns success,
and ice_ptp_port_phy_restart() clears tx.calibrating.
The comment justifies this by saying software only reads indices it
already knows are ready. If the failed read was on a valid entry, though,
the reset zeroes the outstanding-timestamp counter while that entry stays
in memory. That is the same counter/memory desync the comment warns can
prevent new timestamp interrupts.
Before this patch the sweep and reset never ran at runtime, so a
transient sideband failure during a restart could not cause this.
The commit message says "This ensures that we clear any outstanding Tx
timestamp memory", which does not hold when a sweep read fails. Should
the sweep failure be propagated to the caller?
[Severity: Medium]
Does anything keep the Tx timestamp IRQ thread out while the sweep and
soft reset run?
ice_ptp_port_phy_restart() holds adapter->ps_lock. The consumer path does
not take ps_lock, or anything else that excludes the restart:
ice_misc_intr_thread_fn()
ice_ptp_process_ts()
ice_ptp_process_tx_tstamp() / ice_ptp_tx_tstamp_owner()
tx->lock only covers the in_use/skb bookkeeping.
ice_ptp_process_tx_tstamp() reads the ready bitmap once with
ice_get_phy_tx_tstamp_ready(). It then calls ice_read_phy_tstamp() for
each in_use index that bitmap marked ready. Those reads can interleave
with ice_ptp_clear_tx_memory_status_eth56g() and with the roughly 10ms
PHY_REG_GLOBAL toggle.
Suppose the bitmap snapshot is taken before the sweep, and the per-index
read happens after the reset. The IRQ thread then reads an entry that was
already cleared, against a counter that was just zeroed.
The comment on ice_ptp_clear_tx_memory_status_eth56g() says reads outside
the soft reset procedure can put the counter out of sync and prevent new
timestamp interrupts. The IRQ thread could also issue sideband reads to
the port's timestamp block while the reset is asserted.
ice_ptp_restart_all_phy() runs this sequence on every live port on the
adapter, including other PFs' ports. It is called on rebuild, and on
settime64 at the end of the series. Nothing in the series adds
synchronize_irq() or similar to keep the consumer out during this
sequence.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008215614.1987250-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH net v2 11/15] ice: wait for in-flight Tx timestamps before flushing the tracker
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
` (9 preceding siblings ...)
2026-10-08 21:56 ` [PATCH net v2 10/15] ice: E825: perform a soft reset when starting the PHY timer Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-12 23:15 ` netdev-bot+sashiko
2026-10-08 21:56 ` [PATCH net v2 12/15] ice: keep Tx timestamp slots tracked until completion or timeout Tony Nguyen
` (3 subsequent siblings)
14 siblings, 1 reply; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Petr Oros, anthony.l.nguyen, jacob.e.keller, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, richardcochran, horms, Aleksandr Loktionov,
Alexander Nowlin
From: Petr Oros <poros@redhat.com>
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 (per port) for in-flight captures to publish their ready
bits before flushing, so the flush clears them together with the rest.
Note that the ice_ptp_flush_tx_tracker() function was introduced along with
the original E810 support, but that device does not have a ready bitmap.
Only later devices (E822, E825, E830) have the bitmap and potential issues
with internal tracking. Thus, skip the wait for E810 by checking the
tx->has_ready_bitmap flag.
Fixes: 10e4b4a3a3e1 ("ice: check Tx timestamp memory register for ready timestamps")
Signed-off-by: Petr Oros <poros@redhat.com>
Reviewed-by: Maciek Machnikowski <maciej.machnikowski@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp.c | 64 ++++++++++++++++++++++++
1 file changed, 64 insertions(+)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 818e2e265a7e..5220de274819 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -744,6 +744,68 @@ ice_ptp_alloc_tx_tracker(struct ice_ptp_tx *tx)
return 0;
}
+/**
+ * ice_ptp_is_tracker_drained - Check for outstanding timestamps
+ * @pf: Board private structure
+ * @tx: Timestamp tracker structure
+ *
+ * Return: False if there are any timestamps still waiting for hardware;
+ * otherwise true, including when unable to read the ready bitmap.
+ */
+static bool
+ice_ptp_is_tracker_drained(struct ice_pf *pf, struct ice_ptp_tx *tx)
+{
+ struct ice_hw *hw = &pf->hw;
+ bool pending = false;
+ unsigned long flags;
+ u64 tstamp_ready;
+ u8 idx;
+
+ /* If HW reset is ongoing, we can't access SBQ */
+ if (hw->reset_ongoing)
+ return true;
+
+ if (ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready))
+ return true;
+
+ spin_lock_irqsave(&tx->lock, flags);
+ for_each_set_bit(idx, tx->in_use, tx->len) {
+ if (!(tstamp_ready & BIT_ULL(idx + tx->offset))) {
+ pending = true;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ return !pending;
+}
+
+/**
+ * ice_ptp_wait_for_tracker_drain - Wait for PHY to complete timestamps
+ * @pf: Board private structure
+ * @tx: Timestamp tracker structure
+ *
+ * Wait for up to 10 milliseconds for the PHY to complete any outstanding
+ * timestamps before flushing.
+ */
+static void
+ice_ptp_wait_for_tracker_drain(struct ice_pf *pf, struct ice_ptp_tx *tx)
+{
+ bool drained;
+ int err;
+
+ if (!tx->has_ready_bitmap)
+ return;
+
+ err = read_poll_timeout(ice_ptp_is_tracker_drained,
+ drained, drained, 500, 10 * USEC_PER_MSEC, false,
+ pf, tx);
+ if (err) {
+ 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
@@ -760,6 +822,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
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH net v2 11/15] ice: wait for in-flight Tx timestamps before flushing the tracker
2026-10-08 21:56 ` [PATCH net v2 11/15] ice: wait for in-flight Tx timestamps before flushing the tracker Tony Nguyen
@ 2026-10-12 23:15 ` netdev-bot+sashiko
0 siblings, 0 replies; 28+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 23:15 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
maciej.machnikowski, jacob.e.keller, przemyslaw.korba,
grzegorz.nitka, sergey.temerkhanov, arkadiusz.kubalewski, poros,
richardcochran, horms, pmenzel, aleksandr.loktionov,
alexander.nowlin
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1 (1)
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] On E830 the flush still leaves hardware-ready entries
unacknowledged, so the drain does nothing for a device the commit…
- [Low] ice_ptp_rebuild_owner() calls ice_ptp_flush_all_tx_tracker(),
which now runs ice_ptp_wait_for_tracker_drain() on every port of the…
Pre-existing issues:
- [Low] At this commit, ice_ptp_process_tx_tstamp() sets drop_ts =
!link_up (line 592).
--
ice: wait for in-flight Tx timestamps before flushing the tracker
This patch adds a poll to ice_ptp_flush_tx_tracker(). For up to 10 ms per
port, it waits for the PHY to publish ready bits for all in-use Tx
timestamp slots, and only then flushes the tracker. E810 has no ready
bitmap, so it skips the wait.
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
> index 818e2e265a7e5..5220de274819a 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
> @@ -744,6 +744,68 @@ ice_ptp_alloc_tx_tracker(struct ice_ptp_tx *tx)
[ ... ]
> +static void
> +ice_ptp_wait_for_tracker_drain(struct ice_pf *pf, struct ice_ptp_tx *tx)
> +{
> + bool drained;
> + int err;
> +
> + if (!tx->has_ready_bitmap)
> + return;
[Severity: Medium]
Does the drain actually help on E830? E830 has has_ready_bitmap set, so it
enters this wait. The commit message also lists it among the devices that
benefit:
"Only later devices (E822, E825, E830) have the bitmap ... Thus, skip
the wait for E810"
However, ice_clear_phy_tstamp() in ice_ptp_hw.c has no E830 case:
ice_clear_phy_tstamp() {
switch (hw->mac_type) {
case ICE_MAC_E810:
return ice_clear_phy_tstamp_e810(hw, block, idx);
case ICE_MAC_GENERIC:
return ice_clear_phy_tstamp_e82x(hw, block, idx);
case ICE_MAC_GENERIC_3K_E825:
return ice_clear_ptp_tstamp_eth56g(hw, block, idx);
default:
return -EOPNOTSUPP;
}
}
ice_ptp_flush_tx_tracker() ignores that return value, then clears in_use
and frees the skb anyway:
if (!hw->reset_ongoing && (tstamp_ready & BIT_ULL(phy_idx)))
ice_clear_phy_tstamp(hw, tx->block, phy_idx);
ice_ptp_reset_ts_memory(), ice_ptp_port_phy_stop() and
ice_ptp_port_phy_restart() also do nothing for E830.
So on E830, wouldn't an entry that becomes ready during the wait stay set
in E830_PRTMAC_TS_TX_MEM_VALID with no owner? That is the orphaned ready
bit state this patch is trying to prevent.
On the rebuild path the tracker stays initialized. For E830,
ice_ptp_ts_irq() re-arms PFINT_OICR whenever ice_ptp_tx_tstamps_pending()
sees a hardware ready bit.
Could such an orphan keep re-triggering the interrupt, or be picked up as
a stale timestamp by a later request that reuses the index?
The actual effect depends on two hardware details: whether E830 valid bits
clear only when the timestamp is read, and whether CORER resets the PRTMAC
timestamp memory. The E830 case is still missing from
ice_clear_phy_tstamp() at the end of the series.
> +
> + err = read_poll_timeout(ice_ptp_is_tracker_drained,
> + drained, drained, 500, 10 * USEC_PER_MSEC, false,
> + pf, tx);
[Severity: Low]
Will this poll always run until its full 10 ms timeout on the post-reset
rebuild path?
This call chain reaches the poll for every port on the adapter, one port
after another:
ice_rebuild()->ice_ptp_rebuild()->ice_ptp_rebuild_owner()->
ice_ptp_flush_all_tx_tracker()->ice_ptp_flush_tx_tracker()
ice_reset_subtask() clears reset_ongoing before the rebuild:
} else {
/* done with reset. start rebuild */
pf->hw.reset_ongoing = false;
ice_rebuild(pf, reset_type);
So the reset_ongoing early return in ice_ptp_is_tracker_drained() does not
skip the poll.
A CORER/GLOBR/EMPR reset drops the Tx rings. A request whose packet was
dropped that way will never get a ready bit, so
ice_ptp_is_tracker_drained() keeps returning false. That costs about 20
rounds of SBQ ready-bitmap reads per port. On an 8-port adapter it adds
roughly 80 ms to reset recovery.
A Tx hang is a common reason for the reset, and it is also the case most
likely to leave such entries behind.
The wait also seems unnecessary there. Right after the flush,
ice_ptp_rebuild_owner() does:
ice_ptp_flush_all_tx_tracker(pf);
/* Enable quad interrupts */
err = ice_ptp_cfg_phy_interrupt(pf, true, 1);
ice_ptp_cfg_phy_interrupt() calls ice_ptp_reset_ts_memory(), which resets
the E82x timestamp memory. On E825 ports with link up,
ice_ptp_restart_all_phy() then does a PHY soft reset.
Would it make sense to wait only on the release path? That is where new
requests are blocked and captures can still be in flight.
> + if (err) {
> + dev_dbg(ice_pf_to_dev(pf), "Timed out waiting for in-flight Tx timestamps on block %u\n",
> + tx->block);
> + }
> +}
> +
[ ... ]
> @@ -760,6 +822,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);
> +
[Severity: Low]
This is a pre-existing issue and was not introduced by this patch. The new
drain only covers ice_ptp_flush_tx_tracker(). At this commit,
ice_ptp_process_tx_tstamp() can still create the same orphan when the link
goes down:
bool drop_ts = !link_up;
...
if (drop_ts)
goto skip_ts_read;
When a slot is not ready yet, this path clears in_use and frees the skb
without touching the PHY entry. If the capture finishes later, its ready
bit has no owner. That is the case the commit message describes.
The next patch in the series, "ice: keep Tx timestamp slots tracked until
completion or timeout", removes the link-down drop. After that patch, only
the 2 second timeout frees a slot that is not ready. That timeout is
intentional and is documented both in the ice_ptp_process_tx_tstamp()
kernel-doc and in that patch's commit message. So by the end of the series
this no longer applies.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008215614.1987250-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH net v2 12/15] ice: keep Tx timestamp slots tracked until completion or timeout
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
` (10 preceding siblings ...)
2026-10-08 21:56 ` [PATCH net v2 11/15] ice: wait for in-flight Tx timestamps before flushing the tracker Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-12 23:15 ` netdev-bot+sashiko
2026-10-08 21:56 ` [PATCH net v2 13/15] ice: skip reading Tx ready bitmap on ports with no timestamps Tony Nguyen
` (2 subsequent siblings)
14 siblings, 1 reply; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Petr Oros, anthony.l.nguyen, jacob.e.keller, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, richardcochran, horms, Aleksandr Loktionov,
Alexander Nowlin
From: Petr Oros <poros@redhat.com>
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.
Since we're adding a new call to ice_ptp_mark_tx_tracker, notice that the
function previously did not check the tracker initialization state. Fix
this by only accessing the bitmaps if the init field is set.
To avoid continuously re-triggering the IRQ at the end of a processing loop
when we 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 count stale packets as part of the check in the auxiliary work thread.
This way we do not keep spamming the IRQ when the timestamp won't be
reported anyways.
To ensure that forward progress is made on clearing stale timestamps, fix
the check for ice_ptp_maybe_trigger_tx_interrupt to properly apply for
devices that manage their own interrupt, instead of only checking on the
clock owner. In the case of a timestamp which never completes, this does
result in one extra IRQ every 500 msec until the timeout. In some sense
this is extra work, but we need to recheck to ensure that timestamp slots
do not remain locked forever, and the software can't reliably know whether
a given index will or won't complete.
Note that E810 does not have the ready bitmap in its hardware. It is
currently excluded from the ice_ptp_maybe_trigger_tx_interrupt() by a check
against the has_ready_bitmap field. This could lead to an interrupt stall
if every single Tx timestamp index slot becomes stale. However, just
removing the check against has_ready_bitmap would be problematic for the
low latency path which currently is not protected against another IRQ
happening while a request is outstanding. That is a pre-existing issue, but
we should avoid making it worse until the path can be addressed by another
fix. The low latency path also completely bypasses the
ice_ptp_process_tx_tstamp() flow. For now, skip the
ice_ptp_maybe_trigger_tx_interrupt() when ts_ll_int_read is set and the low
latency path is in use. The path still has preexisting issues but this
avoids making it worse.
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 <poros@redhat.com>
Reviewed-by: Maciek Machnikowski <maciej.machnikowski@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
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 | 57 ++++++++++++-----------
drivers/net/ethernet/intel/ice/ice_ptp.h | 8 +++-
3 files changed, 37 insertions(+), 30 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 5220de274819..a5ee8c8edf3d 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -365,9 +365,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 && READ_ONCE(ptp_port->link_up);
}
/**
@@ -554,7 +557,9 @@ void ice_ptp_complete_tx_single_tstamp(struct ice_ptp_tx *tx)
* extremely unlikely that a packet will ever take this long to timestamp. If
* we detect a Tx timestamp request that has waited for this long we assume
* the packet will never be sent by hardware and discard it without reading
- * the timestamp register.
+ * the timestamp register. Note that in the unusual case where the PHY
+ * continuously fails to clear the ready bitmap index, the slot may remained
+ * locked for more than two seconds.
*/
static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
{
@@ -564,7 +569,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;
@@ -582,14 +586,11 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
return;
}
- /* Drop packets if the link went down */
- link_up = READ_ONCE(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;
/* Prevent speculative re-ordering of start and skb */
@@ -875,7 +876,8 @@ ice_ptp_mark_tx_tracker_stale(struct ice_ptp_tx *tx)
unsigned long flags;
spin_lock_irqsave(&tx->lock, flags);
- bitmap_or(tx->stale, tx->stale, tx->in_use, tx->len);
+ if (tx->init)
+ bitmap_or(tx->stale, tx->stale, tx->in_use, tx->len);
spin_unlock_irqrestore(&tx->lock, flags);
}
@@ -1426,6 +1428,9 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
WRITE_ONCE(ptp_port->link_up, linkup);
+ if (!linkup)
+ ice_ptp_mark_tx_tracker_stale(&ptp_port->tx);
+
/* Skip HW writes if reset is in progress */
if (pf->hw.reset_ongoing)
goto out_unlock;
@@ -2824,21 +2829,22 @@ 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)
{
struct ice_port_list *ports = &pf->adapter->ports;
bool have_tstamps = false;
@@ -2851,7 +2857,7 @@ static bool ice_any_port_has_timestamps(struct ice_pf *pf)
if (!kref_get_unless_zero(&port->ref))
continue;
- if (ice_port_has_timestamps(&port->tx))
+ if (ice_port_has_timestamps(&port->tx, in_irq))
have_tstamps = true;
kref_put(&port->ref, ice_ptp_release_port_srcu);
@@ -2864,7 +2870,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;
@@ -2874,11 +2880,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:
@@ -2954,7 +2960,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.
*/
@@ -2984,19 +2990,16 @@ 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)
+ /* Avoid re-triggering OICR on E810 with low latency interrupt path */
+ if (hw->dev_caps.ts_dev_info.ts_ll_int_read)
return;
- if (!ice_pf_src_tmr_owned(pf))
+ if (pf->ptp.tx_interrupt_mode != ICE_PTP_TX_INTERRUPT_SELF &&
+ !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 029ee4612d76..7810fefc546e 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
*
@@ -314,7 +317,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);
@@ -362,7 +365,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
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH net v2 12/15] ice: keep Tx timestamp slots tracked until completion or timeout
2026-10-08 21:56 ` [PATCH net v2 12/15] ice: keep Tx timestamp slots tracked until completion or timeout Tony Nguyen
@ 2026-10-12 23:15 ` netdev-bot+sashiko
0 siblings, 0 replies; 28+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 23:15 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
maciej.machnikowski, jacob.e.keller, przemyslaw.korba,
grzegorz.nitka, sergey.temerkhanov, arkadiusz.kubalewski, poros,
richardcochran, horms, pmenzel, aleksandr.loktionov,
alexander.nowlin
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] This patch adds a sentence to the ice_ptp_process_tx_tstamp()
kernel-doc: "in the unusual case where the PHY continuously fails to…
- [Low] The ice_ptp_maybe_trigger_tx_interrupt() kernel-doc still says
the function will "periodically check if the PHY has outstanding…
Pre-existing issues:
- [Medium] ice_ptp_init() sets link_up from port_info under ps_lock but
sets ptp->state = ICE_PTP_READY only after dropping the lock.
- [Low] Because the has_ready_bitmap gate is removed from
ice_ptp_maybe_trigger_tx_interrupt(), the periodic worker on non-LL…
--
ice: keep Tx timestamp slots tracked until completion or timeout
When the link goes down, outstanding Tx timestamp requests are no longer
dropped. They are marked stale instead, new requests are rejected while
the link is down, and a slot that is not ready yet is freed only after the
two second timeout. The patch also changes ice_ptp_tx_tstamps_pending() so
stale entries are ignored when deciding whether to re-arm the IRQ, and lets
ice_ptp_maybe_trigger_tx_interrupt() run on devices that handle their own
Tx interrupt.
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
> index 5220de274819a..a5ee8c8edf3d6 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
[ ... ]
> @@ -554,7 +557,9 @@ void ice_ptp_complete_tx_single_tstamp(struct ice_ptp_tx *tx)
> * extremely unlikely that a packet will ever take this long to timestamp. If
> * we detect a Tx timestamp request that has waited for this long we assume
> * the packet will never be sent by hardware and discard it without reading
> - * the timestamp register.
> + * the timestamp register. Note that in the unusual case where the PHY
> + * continuously fails to clear the ready bitmap index, the slot may remained
> + * locked for more than two seconds.
> */
> static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
> {
[Severity: Low]
Does this new comment describe what the code does at this commit?
In ice_ptp_process_tx_tstamp(), nothing re-reads the PHY ready bitmap
after ice_read_phy_tstamp() and before the slot is released:
err = ice_read_phy_tstamp(hw, tx->block, phy_idx, &raw_tstamp);
if (err && !drop_ts)
continue;
...
skip_ts_read:
...
clear_bit(idx, tx->in_use);
Suppose the PHY does not clear the ready bit, for example for an entry
with VALID clear that was captured while P_REG_TX_OR=0. Then the index is
freed and the skb released, which leaves a set ready bit with no in_use
owner. The slot does not stay locked.
At this commit ice_ptp_tx_tstamps_pending(pf, true) still falls back to
the hardware check:
/* Check hardware indicator */
ret = ice_check_phy_tx_tstamp_ready(hw);
That would re-arm PFINT_OICR_TSYN_TX for a bit the processing loop never
visits.
A later patch in this series, "ice: don't clear in_use until HW clears
ready bitmap", adds the "verify ready bit cleared" re-read. That re-read
keeps the index in_use and marks it stale. The same patch also removes the
hardware fallback from ice_ptp_tx_tstamps_pending(). After the full series
the comment matches the code.
Could this comment move into that later patch so each commit is consistent
on its own?
[ ... ]
> @@ -1426,6 +1428,9 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
>
> WRITE_ONCE(ptp_port->link_up, linkup);
>
> + if (!linkup)
> + ice_ptp_mark_tx_tracker_stale(&ptp_port->tx);
> +
> /* Skip HW writes if reset is in progress */
> if (pf->hw.reset_ongoing)
> goto out_unlock;
[Severity: Medium]
This is a pre-existing issue and this patch did not introduce it. This
patch does make link_up more important, though.
ice_ptp_init() seeds link_up while holding ps_lock, but it publishes
ICE_PTP_READY only after dropping the lock:
ice_ptp_init() {
...
mutex_lock(&pf->adapter->ps_lock);
if (pf->hw.port_info)
WRITE_ONCE(ptp->port.link_up, ...);
ice_ptp_port_phy_restart(&ptp->port);
mutex_unlock(&pf->adapter->ps_lock);
ice_ptp_cfg_tx_interrupt(pf);
ptp->state = ICE_PTP_READY;
...
}
ice_ptp_link_change() checks the state without holding the lock:
if (pf->ptp.state != ICE_PTP_READY)
return;
ice_init() starts the service timer, and the netdev is registered before
ice_init_features()->ice_ptp_init() runs, so link events can arrive during
this window. Any link event in that window is dropped.
With this patch, ice_ptp_is_tx_tracker_up() and the link-down stale marking
both depend on link_up. A stale link_up=false now means every new request
is rejected until the next link flap. The race itself came from the
earlier commit "ice: call PTP link change only from link events".
Should ptp->state be set while ps_lock is held, or should link_up be
re-seeded after the state becomes ICE_PTP_READY?
[ ... ]
> @@ -2984,19 +2990,16 @@ 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)
> + /* Avoid re-triggering OICR on E810 with low latency interrupt path */
> + if (hw->dev_caps.ts_dev_info.ts_ll_int_read)
> return;
>
[Severity: Low]
This is a pre-existing issue and was not introduced by this patch. It is
a kernel-doc wording problem.
With the has_ready_bitmap gate gone, the periodic worker on non-LL E810
now reaches ice_check_phy_tx_tstamp_ready_e810(). At this commit it gets
there through ice_ptp_tx_tstamps_pending(). Later in the series it calls
it directly. The kernel-doc of that helper says it "should not be called
for E810 hardware", but the body is only:
static int ice_check_phy_tx_tstamp_ready_e810(struct ice_hw *hw)
{
return 0;
}
ice_misc_intr_thread_fn() already reached it on E810 before this patch,
so nothing changes at runtime.
Should that kernel-doc be updated, now that this is a regular call path?
> - if (!ice_pf_src_tmr_owned(pf))
> + if (pf->ptp.tx_interrupt_mode != ICE_PTP_TX_INTERRUPT_SELF &&
> + !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);
[Severity: Low]
Should the kernel-doc of ice_ptp_maybe_trigger_tx_interrupt() be updated
to match the new trigger condition? It still says:
* To avoid getting permanently stuck, periodically check if the PHY has
* outstanding timestamp data. If so, trigger an interrupt from software to
* process this data.
With in_irq=false, ice_port_has_timestamps() returns:
return !bitmap_empty(tx->in_use, tx->len);
That is true for any outstanding software request, including stale
requests and requests the PHY has not captured yet. So the interrupt now
fires on every 500 msec tick even when the PHY has no data. The commit
message describes this ("one extra IRQ every 500 msec until the timeout"),
but the function comment was not updated.
At the end of the series the condition is
"ret > 0 || ice_ptp_tx_tstamps_pending(pf, false)" and the comment text is
the same, so the mismatch remains.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008215614.1987250-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH net v2 13/15] ice: skip reading Tx ready bitmap on ports with no timestamps
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
` (11 preceding siblings ...)
2026-10-08 21:56 ` [PATCH net v2 12/15] ice: keep Tx timestamp slots tracked until completion or timeout Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-08 21:56 ` [PATCH net v2 14/15] ice: don't clear in_use until HW clears ready bitmap Tony Nguyen
2026-10-08 21:56 ` [PATCH net v2 15/15] ice: Recalibrate PHY after settime64 on E825-C Tony Nguyen
14 siblings, 0 replies; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Jacob Keller, anthony.l.nguyen, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, poros, richardcochran, horms,
Alexander Nowlin
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 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.
Skipping the check has the highest impact on E822 an E825 devices which
iterate all ports in a single thread, but it is applied universally to all
device types since the extra read is unnecessary regardless.
The average latency of a Tx timestamp is impacted by several factors
including system load, the number of timestamp requests, and some random
factors that are difficult to control. However for comparison on my system
with these changes, while operating ptp4l on a single port with a sync rate
of 16/second:
Before: mean 317.43 microseconds, standard deviation 34.51
After: mean 189.71 microseconds, standard deviation 43.24
Of course this level of improvement may not be indicative of a production
setup as one might expect timestamps to be operating out of multiple ports
on the device. However, even in that case an improvement is still likely
depending on the actual timestamp rates.
Fixes: d938a8cca88a ("ice: Auxbus devices & driver for E822 TS")
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@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 a5ee8c8edf3d..9dc0b5fa3319 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -576,7 +576,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
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH net v2 14/15] ice: don't clear in_use until HW clears ready bitmap
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
` (12 preceding siblings ...)
2026-10-08 21:56 ` [PATCH net v2 13/15] ice: skip reading Tx ready bitmap on ports with no timestamps Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-12 23:15 ` netdev-bot+sashiko
2026-10-08 21:56 ` [PATCH net v2 15/15] ice: Recalibrate PHY after settime64 on E825-C Tony Nguyen
14 siblings, 1 reply; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Jacob Keller, anthony.l.nguyen, maciej.machnikowski,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, poros, richardcochran, horms,
Alexander Nowlin
From: Jacob Keller <jacob.e.keller@intel.com>
During a link down transition, there is a small window where hardware
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 keep the index locked. The
index will be re-checked once another interrupt occurs (either from a real
timestamp or from the watchdog kick). Marking the packet as stale makes
sense since we know this begins happening when link is going down.
If the ice_get_phy_tx_tstamp_ready() fails, treat it the same as if the
hardware hadn't cleared the bitmap. Continuing here is safe since the
ice_get_phy_tx_tstamp_ready() device implementations do not update the
output parameter except on success. Note that "transient" errors to access
the PHY will result in marking packets as stale. This is the simplest
approach and avoids risking a potential reuse of the stuck ready bit.
The extra reads of the timestamp ready bitmap are saved in the same
tstamp_ready field. This effectively updates the ready bitmap for all
timestamps remaining in the loop. This is safe, and actually increases the
changes that a given timestamp will be processed by the loop in the event
that a timestamp completed after the initial read. Update the comments to
remove some text that might imply otherwise.
This flow has been observed on E825, but allowing the driver to free an
index which is not cleared would be incorrect regardless of which device
type it occurs on. Thus, this re-read is applied to all device types.
In the unlikely event that a timestamp has timed out the 2 second wait
*and* somehow suddenly has its ready bit set but unable to clear on read,
this could accidentally increment the timeout counter. It is intentional
that we do *not* release the index even in a timed out case, as we must not
allow reuse of that index until we can be certain it has cleared. Instead,
refactor so that the timeout counter is only incremented once the index is
released, (after the skip_tx_read label). This ensures that we don't double
count timeouts or count them prematurely. The "stuck" ready indexes remain
locked *indefinitely* until the hardware reaches a state where the clear
works.
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: 189.71 microseconds mean, stdev 43.24
after: 195.92 microseconds mean, stdev 25.28
* a C program generating 16 timestamp requests every 10 milliseconds on
two different ports:
before: 457.11 microseconds mean, stdev 180.63
after: 717.77 microseconds mean, stdev 321.58
In the normal work flows this comes with about a 10-20 microsecond penalty on
the average, and the standard deviation remains similar (with some variance
between run to run comparison).
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. The high standard deviation indicates a very high variance in
timestamp latency, with many timestamps completing in the usual time but
some taking significantly longer when multiple timestamps are outstanding
in a single IRQ.
Ultimately, *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 <alexander.nowlin@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp.c | 78 ++++++++++++------------
1 file changed, 39 insertions(+), 39 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 9dc0b5fa3319..8d302b500a39 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -588,9 +588,9 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
for_each_set_bit(idx, tx->in_use, tx->len) {
struct skb_shared_hwtstamps shhwtstamps = {};
+ bool drop_ts = false, timeout = false;
u8 phy_idx = idx + tx->offset;
u64 raw_tstamp = 0, tstamp;
- bool drop_ts = false;
struct sk_buff *skb;
/* Prevent speculative re-ordering of start and skb */
@@ -599,18 +599,11 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
/* Drop packets which have waited for more than 2 seconds */
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++;
+ timeout = true;
}
- /* Only read a timestamp from the PHY if its marked as ready
- * by the tstamp_ready register. This avoids unnecessary
- * reading of timestamps which are not yet valid. This is
- * important as we must read all timestamps which are valid
- * and only timestamps which are valid during each interrupt.
- * If we do not, the hardware logic for generating a new
- * interrupt can get stuck on some devices.
+ /* Only read a timestamp from the PHY if it is marked as ready
+ * by the timestamp_ready register.
*/
if (tx->has_ready_bitmap &&
!(tstamp_ready & BIT_ULL(phy_idx))) {
@@ -626,6 +619,20 @@ 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_bit(idx, tx->in_use) &&
+ !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
@@ -642,6 +649,9 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
drop_ts = true;
skip_ts_read:
+ if (timeout)
+ pf->ptp.tx_hwtstamp_timeouts++;
+
spin_lock_irqsave(&tx->lock, flags);
if (!tx->has_ready_bitmap && raw_tstamp)
tx->tstamps[idx].cached_tstamp = raw_tstamp;
@@ -2837,10 +2847,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);
+ }
}
}
@@ -2872,41 +2886,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;
}
/**
@@ -2990,6 +2981,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;
/* Avoid re-triggering OICR on E810 with low latency interrupt path */
if (hw->dev_caps.ts_dev_info.ts_ll_int_read)
@@ -2999,7 +2991,15 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
!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
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH net v2 14/15] ice: don't clear in_use until HW clears ready bitmap
2026-10-08 21:56 ` [PATCH net v2 14/15] ice: don't clear in_use until HW clears ready bitmap Tony Nguyen
@ 2026-10-12 23:15 ` netdev-bot+sashiko
0 siblings, 0 replies; 28+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 23:15 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
maciej.machnikowski, jacob.e.keller, przemyslaw.korba,
grzegorz.nitka, sergey.temerkhanov, arkadiusz.kubalewski, poros,
richardcochran, horms, pmenzel, aleksandr.loktionov,
alexander.nowlin
Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider.
Critical: 0 · High: 2 · Medium: 3 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] ice: Tx timestamp SKB leak when PHY ready bit is stuck
- [High] net/ice: IRQ storm due to PHY read failure bypassing stale
cleanup
- [Medium] ice_ptp_maybe_trigger_tx_interrupt()
(drivers/net/ethernet/intel/ice/ice_ptp.c:2994-3002) now calls…
- [Medium] This patch removes the hardware ready-bitmap fallback from the
IRQ-thread re-arm decision.
- [Medium] The patch now deliberately leaves a PHY Tx ready bit set while
it keeps the index in_use and stale (ice_ptp.c:622-634), and it also…
- [Low] In ice_ptp_process_tx_tstamp() (ice_ptp.c:622-634), suppose
ice_read_phy_tstamp() succeeds and the hardware clears the ready bit,…
--
ice: don't clear in_use until HW clears ready bitmap
After each PHY timestamp read, this patch re-reads the Tx timestamp ready
bitmap. If hardware did not clear the ready bit, the index stays locked and
is marked stale. The patch also removes the hardware ready bitmap check from
ice_ptp_tx_tstamps_pending(), stops the IRQ re-arm for trackers that are not
up, and moves the hardware check into the periodic watchdog.
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
> index 9dc0b5fa33198..8d302b500a399 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
[ ... ]
> @@ -626,6 +619,20 @@ 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_bit(idx, tx->in_use) &&
> + !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]
The commit message says a failed ice_get_phy_tx_tstamp_ready() is
treated the same as a ready bit that didn't clear. That holds for
this re-read, but not for the first read at the top of
ice_ptp_process_tx_tstamp():
if (tx->has_ready_bitmap) {
err = ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready);
if (err)
return;
}
On that error the function returns before the loop. Nothing is
marked stale and the 2 second timeout is never checked.
With the link up, the tracker passes ice_ptp_is_tx_tracker_up(), and
bitmap_andnot(in_use, stale) is still non-empty. So
ice_misc_intr_thread_fn() sees ice_ptp_tx_tstamps_pending(pf, true)
return true and writes PFINT_OICR_TSYN_TX_M again right away.
If the sideband read keeps failing, doesn't this turn into the same
tight IRQ loop the patch is trying to remove? The pending skbs would
also never time out.
The early return was there before this patch. The new tracker-up
check only covers link down and calibrating, so it doesn't help in
this case.
Should the initial failure be handled like the re-read failure? For
example, the in_use entries could be marked stale while timed out
entries are still released. Note that tstamp_ready is uninitialized
on that path, so the loop can't simply fall through. Or is a
persistent read failure with the link up not expected to happen?
[Severity: High]
When the re-read still shows the ready bit set, this continue leaves
both the in_use bit and tx->tstamps[idx].skb in place. Is holding on
to the skb here intended?
Keeping the index locked makes sense. The problem is that the skb now
has no way out of the tracker while the PHY stays stuck, and the 2
second timeout no longer helps.
With the ready bit still set, a timed out entry doesn't take the goto
skip_ts_read branch. It reads the PHY again, fails this same check,
and hits this continue again. Before this patch the timeout path
always reached skip_ts_read and freed the skb.
ice_ptp_request_ts() stores a reference to the original skb taken
with skb_get(). The sending socket therefore stays charged in
sk_wmem_alloc for as long as the entry sits here.
The commit message says stuck indexes stay locked until the PHY is
reset on link up. As far as I can tell, if the link never comes back,
the skb is only freed by a reset or a driver unload. Until then the
owning socket can't be destroyed.
An unprivileged user in a container can request Tx timestamps. If
that happens around a link drop, the netns teardown could be left
hanging.
Could the skb be pulled out of tx->tstamps[idx] and freed (or
completed without a timestamp) under tx->lock once the timeout
expires? The in_use bit could still stay set so the index isn't
reused. Or is there another path that releases these skbs that I'm
missing?
[Severity: Low]
Suppose ice_read_phy_tstamp() succeeds and hardware clears the ready bit,
but this second ice_get_phy_tx_tstamp_ready() call hits a transient
sideband error. What happens then?
The index gets marked stale and stays in_use. On the next pass the ready
bit reads as clear, so ice_ptp_process_tx_tstamp() takes this earlier
branch:
if (tx->has_ready_bitmap &&
!(tstamp_ready & BIT_ULL(phy_idx))) {
if (drop_ts)
goto skip_ts_read;
continue;
}
That branch never checks the stale bit. The slot seems to stay locked until
the 2 second timeout sets drop_ts. When it is finally released, timeout is
true and pf->ptp.tx_hwtstamp_timeouts gets incremented.
Would this count a PHY access error as a Tx timestamp timeout, and hold the
slot for up to 2 seconds?
The commit message says transient errors mark packets stale. It doesn't
mention the 2 second hold or the timeout being counted.
> +
> ice_trace(tx_tstamp_fw_done, tx->tstamps[idx].skb, idx);
[ ... ]
> @@ -2837,10 +2847,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);
[Severity: Medium]
The verify path in ice_ptp_process_tx_tstamp() now leaves a ready bit set
while the index stays in_use and stale. Can a stuck index on one port delay
Tx timestamps for healthy ports that share the same interrupt source?
The comment above ice_ptp_maybe_trigger_tx_interrupt() says:
* timestamp data from the PHY. It will not interrupt again until all
* current timestamp data is read.
An earlier commit in this series also says an unread ready bit means
delivery for the whole quad degrades to the periodic work.
On E82x, the timestamp interrupt enable and threshold are per quad. See the
single Q_REG_TX_MEM_GBL_CFG in ice_phy_cfg_intr_e82x(). On E825, the TSYN_TX
causes for all ports go to the clock owner in ICE_PTP_TX_INTERRUPT_ALL mode.
Before this patch, the hardware check in the IRQ re-arm path caused the
storm, but it also kept serving the other ports in the quad within
microseconds.
The in_irq branch now looks only at software state. Would new timestamps on
healthy ports then be handled only when ice_ptp_periodic_work() writes
PFINT_OICR every 500 ms?
On E825, ice_ptp_link_change() only restarts the PHY on link up, so this
could last as long as the bad port's link stays down. The latency numbers in
the commit message only cover healthy links.
> - else
> + } else {
> return !bitmap_empty(tx->in_use, tx->len);
> + }
> }
> }
>
> @@ -2872,41 +2886,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)
> {
[ ... ]
> - /* 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: Medium]
Removing this hardware fallback also affects the in_irq callers:
ice_misc_intr_thread_fn() and the E830 top half in ice_ptp_ts_irq(). Can a
stale index on a tracker that is up miss its re-arm?
Stale entries can exist while the tracker and link are both up, for example
after a PHC settime/adjtime:
ice_ptp_reset_cached_phctime() {
...
ice_ptp_mark_tx_tracker_stale(&pf->ptp.port.tx);
}
Such a stale index might become ready after ice_ptp_process_tx_tstamp() has
already passed it in the loop, or after the last refresh of tstamp_ready.
If so, it isn't read in that pass. Then ice_misc_intr_thread_fn() runs:
if (ice_ptp_tx_tstamps_pending(pf, true)) {
...
wr32(hw, PFINT_OICR, PFINT_OICR_TSYN_TX_M);
The software check is now bitmap_andnot(in_use, stale), which is empty, so
no re-arm happens. Before this patch, ice_check_phy_tx_tstamp_ready() would
have seen the ready bit and re-armed.
An earlier commit in this series says the PHY doesn't raise another Tx
timestamp interrupt until every outstanding ready bit is read. If that
holds, would new non-stale timestamps then wait for ice_ptp_periodic_work()
(500 ms)? That is well past ptp4l's default 10 ms tx_timestamp_timeout.
The commit message justifies dropping the hardware check for trackers that
are calibrating or link down. It doesn't cover trackers that are up and have
stale entries whose ready bits are set.
[ ... ]
> @@ -2999,7 +2991,15 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
> !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)) {
[Severity: Medium]
Does this early return change when the watchdog kicks the interrupt?
Before this patch, ice_ptp_tx_tstamps_pending(pf, false) checked the
software in_use bitmap first. It only read the PHY when nothing was pending
in software.
Now a PHY read error returns before the software check runs. In that case
PFINT_OICR_TSYN_TX_M is not written, even if in_use has entries.
ice_check_phy_tx_tstamp_ready_eth56g() returns the first error from any
port, and ice_check_phy_tx_tstamp_ready_e82x() does the same over quads:
for (port = 0; port < hw->ptp.num_lports; port++) {
...
err = ice_get_phy_tx_tstamp_ready(hw, port, &tstamp_ready);
if (err)
return err;
In ICE_PTP_TX_INTERRUPT_ALL mode, one port or quad with a failing status
read would then stop the watchdog kick for every port.
After this patch, the in_irq path skips stale entries and trackers that are
down. The 2 second timeout is only checked inside
ice_ptp_process_tx_tstamp().
While the read keeps failing, would those in_use entries and their skbs stay
unreleased until a real PHY interrupt arrives?
Would it make sense to check the software tracker first, or to ignore the
PHY error when in_use is not empty? The commit message says the watchdog
continues to check the hardware bitmap. It doesn't mention this change in
ordering.
> dev_dbg(dev, "PTP periodic task detected waiting timestamps. Triggering Tx timestamp interrupt now.\n");
>
> wr32(hw, PFINT_OICR, PFINT_OICR_TSYN_TX_M);
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008215614.1987250-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH net v2 15/15] ice: Recalibrate PHY after settime64 on E825-C
2026-10-08 21:55 [PATCH net v2 00/15][pull request] ice: E82x: timestamp processing logic fixes Tony Nguyen
` (13 preceding siblings ...)
2026-10-08 21:56 ` [PATCH net v2 14/15] ice: don't clear in_use until HW clears ready bitmap Tony Nguyen
@ 2026-10-08 21:56 ` Tony Nguyen
2026-10-12 23:15 ` netdev-bot+sashiko
14 siblings, 1 reply; 28+ messages in thread
From: Tony Nguyen @ 2026-10-08 21:56 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Maciek Machnikowski, anthony.l.nguyen, jacob.e.keller,
przemyslaw.korba, grzegorz.nitka, sergey.temerkhanov,
arkadiusz.kubalewski, poros, richardcochran, horms, Paul Menzel,
Aleksandr Loktionov, Alexander Nowlin
From: Maciek Machnikowski <maciej.machnikowski@intel.com>
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 <maciej.machnikowski@intel.com>
Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Tested-by: Alexander Nowlin <alexander.nowlin@intel.com>
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 8d302b500a39..a5fe05d70461 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -2086,8 +2086,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
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH net v2 15/15] ice: Recalibrate PHY after settime64 on E825-C
2026-10-08 21:56 ` [PATCH net v2 15/15] ice: Recalibrate PHY after settime64 on E825-C Tony Nguyen
@ 2026-10-12 23:15 ` netdev-bot+sashiko
0 siblings, 0 replies; 28+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 23:15 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
maciej.machnikowski, jacob.e.keller, przemyslaw.korba,
grzegorz.nitka, sergey.temerkhanov, arkadiusz.kubalewski, poros,
richardcochran, horms, pmenzel, aleksandr.loktionov,
alexander.nowlin
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 2 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The fix only partly ports the E82x settime recalibration
sequence to E825-C.
- [Medium] The new E825-C recalibration call in ice_ptp_settime64() runs
ice_start_phy_timer_eth56g() after the PTP hardware semaphore has been…
- [Low] On E825-C, ice_ptp_settime64() can return 0 even though PHY
recalibration failed, and the port is left unable to take Tx timestamp…
--
ice: Recalibrate PHY after settime64 on E825-C
This patch makes ice_ptp_settime64() call ice_ptp_restart_all_phy() on
E825-C (ICE_MAC_GENERIC_3K_E825), as it already did on E822/E823, so the
PHY is recalibrated after the clock time is set. Without this, ptp4l
converges slowly after large time jumps because the PHY timestamps are
incorrect.
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
> index 8d302b500a399..a5fe05d704617 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
> @@ -2086,8 +2086,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: Medium]
Does E825-C get only half of the E82x settime sequence here?
On E82x, ice_ptp_settime64() marks timestamps invalid before it writes
the new time:
if (hw->mac_type == ICE_MAC_GENERIC) {
err = ice_ptp_clear_phy_offset_ready_e82x(hw);
...
}
That check is still E82x-only. On E825-C, PHY_REG_RX_OFFSET_READY stays
set and tx.calibrating stays false through ice_ptp_write_init(). They
only change later, one port at a time:
ice_ptp_restart_all_phy()
ice_ptp_port_phy_restart()
tx.calibrating = true; ice_ptp_mark_tx_tracker_stale()
ice_start_phy_timer_eth56g()
ice_stop_phy_timer_eth56g()
PHY_REG_RX_OFFSET_READY = 0
The window between INIT_TIME and that point covers
ice_ptp_reset_cached_phctime(), ice_ptp_enable_all_perout(), taking
ps_lock, and the full restart of every earlier port in the adapter list.
Each restart calls ice_ptp_phy_soft_reset_eth56g(), which has two
usleep_range(5000, 6000) calls plus a sync. Later ports therefore wait
10 ms or more for each earlier port.
The commit message says the PHY timestamps are incorrect until
recalibration. During this window the cached PHC time has already moved,
and ice_ptp_get_rx_hwts() only checks the valid bit:
if (!(rx_desc->wb.time_stamp_low & ICE_PTP_TS_VALID))
return 0;
Could uncalibrated Rx timestamps that still have the valid bit set reach
the stack here?
Tx timestamp requests made in the window could also complete before
that port's ice_ptp_mark_tx_tracker_stale() call.
ice_ptp_reset_cached_phctime() only marks pf->ptp.port.tx stale, not
the other ports in the adapter.
Would it make sense to clear RX_OFFSET_READY and set tx.calibrating on
all E825-C ports before ice_ptp_write_init(), as the E82x path does?
[Severity: Low]
Can a failed E825-C recalibration go unreported here?
In ice_ptp_port_phy_restart(), the ICE_MAC_GENERIC_3K_E825 case does:
ptp_port->tx.calibrating = true;
...
err = ice_start_phy_timer_eth56g(hw, port);
if (err)
break;
ice_start_phy_timer_eth56g() clears PHY_REG_RX_OFFSET_READY first and
can fail after that, for example with -EBUSY when ice_ptp_lock() fails,
or on a sideband read/write error. On that path tx.calibrating stays set
and RX_OFFSET_READY is not restored.
ice_ptp_restart_all_phy() returns void and drops the per-port error, so
ice_ptp_settime64() still returns 0 to the PTP core. The only record is
a dev_err() that says "on link-up", even when the call came from
settime.
After that, ice_ptp_is_tx_tracker_up() refuses Tx timestamp requests on
the port:
return tx->init && !tx->calibrating && READ_ONCE(ptp_port->link_up);
This lasts until a later restart succeeds, for example after a link
toggle.
The ignored return value already existed on the E82x path. This change
makes the failure reachable from clock_settime() and from large
clock_adjtime() calls on E825-C.
Should ice_ptp_restart_all_phy() pass the error back to
ice_ptp_settime64()?
[Severity: Medium]
Can the new E825-C restart from settime race with a concurrent clock
update on the shared source timer command register?
ice_ptp_restart_all_phy() runs after ice_ptp_unlock(), and
ice_start_phy_timer_eth56g() writes GLTSYN_CMD before it takes the PTP
semaphore. On secondary PFs that write goes to the primary hw:
err = ice_ptp_phy_soft_reset_eth56g(hw, port);
if (err)
return err;
ice_ptp_src_cmd(hw, ICE_PTP_NOP);
...
if (!ice_ptp_lock(hw)) {
ice_ptp_tmr_cmd_to_src_reg() maps ICE_PTP_NOP to GLTSYN_CMD_READ_TIME.
At the same time, another caller can hold the semaphore inside
ice_ptp_tmr_cmd(): a second settime, adjtime through ice_ptp_write_adj(),
or adjfine through ice_ptp_write_incval_locked():
ice_ptp_src_cmd(hw, cmd);
err = ice_ptp_port_cmd(hw, cmd);
...
ice_ptp_exec_tmr_cmd(hw);
Suppose the unlocked NOP write lands between ice_ptp_src_cmd() and
ice_ptp_exec_tmr_cmd(). Wouldn't the source timer then run READ_TIME
while the PHY ports still apply INIT_TIME, ADJ_TIME or INIT_INCVAL?
The concurrent caller would return success but its PHC change would be
lost. Later ice_sync_phy_timer_eth56g() calls would then align the PHYs
to the unchanged PHC.
Nothing serializes these two paths. ptp_clock_settime() and
ptp_clock_adjtime() hold only the posix-clock read-side rwsem, and the
time-write part of settime/adjtime does not take ps_lock.
The unlocked write is older than this patch and could already be reached
from the E825-C link-up restart in ice_ptp_link_change(). This change
adds a path to it from userspace clock_settime() and large
clock_adjtime() calls.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008215614.1987250-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 28+ messages in thread