Re: [PATCH net-next 1/3] ice: monitor TSPLL lock from PTP periodic worker
From: netdev-bot+sashiko@kernel.org
Date: 2026-09-11 10:27:10
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: Critical: 0 · High: 0 · Medium: 2 · Low: 3 - [Medium] ice_ptp_tspll_monitor()'s recovery cadence and its log rate- limit (ICE_TSPLL_LOG_INTERVAL = 120, documented as "~60 s at normal… - [Medium] The lock-lost dev_warn() and the lock-acquired dev_info() in ice_ptp_tspll_monitor() are effectively unrate-limited when the TSPLL… - [Low] Commit message misdescribes the tree state: it says the cached lock state is added "so a follow-up change can consume it from the… - [Low] ice_ptp_tspll_monitor() is invoked at the top of ice_ptp_periodic_work(), ahead of ice_ptp_update_cached_phctime(). - [Low] ice_ptp_init() initializes only ptp->tspll_locked = true and never resets the newly added ptp->tspll_lock_retries, whereas the… -- ice: monitor TSPLL lock from PTP periodic worker Adds ice_ptp_tspll_monitor() to the PTP periodic worker so E825 owner PFs poll the TSPLL lock status over CGU registers and restart the PLL when the lock is lost. The observed state is cached in pf->ptp.tspll_locked, and three TSPLL helpers are added/exported for the read, the restart and the clock source string. A few questions about the new monitor and about the changelog below.
Cache the observed lock state in pf->ptp.tspll_locked using WRITE_ONCE()/READ_ONCE() so a follow-up change can consume it from the DPLL periodic worker (for user-space notification via dpll_device_change_ntf()) and drop the redundant poll+recovery from that path.
[Severity: Low] Is there really a redundant poll+recovery in that path today? At the baseline, ice_dpll.c has no reference to ICE_CGU_RO_LOCK, ICE_CGU_R23_R24_TSPLL_ENABLE or any lost-lock/restart helper; grepping the ice/ directory matches only ice_tspll.c and ice_common.h. ice_dpll_periodic_work() only calls ice_dpll_update_state(), which polls EEC/PPS state over AQ. The follow-up patch in this series adds a consumer of the new cache (ice_dpll_periodic_work_e825() -> ice_dpll_tspll_lock_status_get() -> READ_ONCE(pf->ptp.tspll_locked)) but does not remove any TSPLL poll or recovery. Could the wording be adjusted to say this adds new TSPLL monitoring and recovery rather than relocating existing recovery out of the DPLL worker? The same claim is repeated in the new kernel-doc for @tspll_locked in ice_ptp.h.
quoted hunk ↗ jump to hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c index eaec36ab6ae3..4b1040d09f70 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c@@ -4,8 +4,11 @@ #include "ice.h" #include "ice_lib.h" #include "ice_trace.h" +#include "ice_tspll.h" #include "ice_txclk.h" +#define ICE_TSPLL_LOG_INTERVAL 120 + static const char ice_pin_names[][64] = { "SDP0", "SDP1",
[ ... ]
quoted hunk ↗ jump to hunk
@@ -2849,6 +2852,67 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf) } } +/** + * ice_ptp_tspll_monitor - poll and recover TSPLL lock on E825 owner PFs + * @pf: Board private structure + * + * Called from the PTP periodic worker. On E825 devices that own the source + * timer, poll the TSPLL lock status via CGU registers and trigger a restart + * if the lock has been lost. The result is cached in @pf->ptp.tspll_locked + * so it can be consumed by the DPLL periodic worker via READ_ONCE(). + * + * TSPLL lock is critical for PHC operation and must be monitored regardless + * of whether DPLL init succeeded or CONFIG_DPLL is enabled. Placing the + * monitor here makes recovery independent of the dpll subsystem. + * + * AQ read errors are rate-limited and do not stop monitoring. Lock-lost + * events are logged every 120 retries (~60 s at normal poll rate) to + * surface persistent failures without flooding the log. + */ +static void ice_ptp_tspll_monitor(struct ice_pf *pf) +{ + bool lock_lost; + int err; + + if (pf->hw.mac_type != ICE_MAC_GENERIC_3K_E825 || + !ice_pf_src_tmr_owned(pf)) + return; + + err = ice_tspll_lost_lock_e825c(&pf->hw, &lock_lost); + if (err) { + dev_err_ratelimited(ice_pf_to_dev(pf), + "Failed reading TimeSync PLL lock status (err: %d). Retrying.\n", + err); + return; + } + + if (lock_lost) { + WRITE_ONCE(pf->ptp.tspll_locked, false); + if (!(pf->ptp.tspll_lock_retries % ICE_TSPLL_LOG_INTERVAL)) + dev_warn(ice_pf_to_dev(pf), + "TimeSync PLL lock lost. Retrying to acquire lock.\n"); + err = ice_tspll_restart_e825c(&pf->hw); + if (err) + dev_err_ratelimited(ice_pf_to_dev(pf), + "Failed to restart TimeSync PLL (err: %d).\n", + err); + pf->ptp.tspll_lock_retries++;
[Severity: Medium] The kernel-doc above says lock-lost events are logged "every 120 retries (~60 s at normal poll rate)", and the new @tspll_lock_retries documentation in ice_ptp.h repeats "~every 120 retries / ~60 s". Does that hold when the worker requeues itself early? ice_ptp_periodic_work() ends with: /* Run twice a second or reschedule if phc update failed */ kthread_queue_delayed_work(ptp->kworker, &ptp->work, msecs_to_jiffies(err ? 10 : 500)); and ice_ptp_update_cached_phctime() returns an error whenever ICE_CFG_BUSY is already set: if (test_and_set_bit(ICE_CFG_BUSY, pf->state)) return -EAGAIN; ICE_CFG_BUSY is held for the whole duration of ice_set_ringparam(), ice_set_link_ksettings() and queue count reconfiguration. If one of those overlaps a lost TSPLL lock, the monitor runs every ~10 ms rather than every 500 ms. Each of those ticks calls ice_tspll_restart_e825c(), which clears ICE_CGU_R23_R24_TSPLL_ENABLE, sleeps 1-2 ms with the source PLL disabled and re-enables it. Is it intended that the source PLL is held disabled for 10-20% of that window, that 3-4 sideband transactions are issued every 10 ms, and that the documented ~60 s warning interval collapses to ~1.2 s while "after %u retries" no longer maps to a fixed time base? The later commit in this series that wraps the monitor body in pf->dplls.lock leaves this cadence unchanged.
+ } else {
+ if (pf->ptp.tspll_lock_retries) {
+ const char *src_str = "unknown";
+ enum ice_clk_src clk_src;
+
+ if (!ice_tspll_get_clk_src(&pf->hw, &clk_src))
+ src_str = ice_tspll_clk_src_str(clk_src);
+ dev_info(ice_pf_to_dev(pf),
+ "TimeSync PLL lock acquired with %s clock source after %u retries.\n",
+ src_str, pf->ptp.tspll_lock_retries);
+ }
+ WRITE_ONCE(pf->ptp.tspll_locked, true);
+ pf->ptp.tspll_lock_retries = 0;
+ }
+}
[Severity: Medium]
Because this branch resets pf->ptp.tspll_lock_retries to zero, the
'% ICE_TSPLL_LOG_INTERVAL' gate on the dev_warn() is satisfied again on the
very next lock-lost tick.
For a reference clock that alternates between locked and unlocked, does this
emit one dev_warn("TimeSync PLL lock lost...") plus one unrate-limited
dev_info("TimeSync PLL lock acquired ... after %u retries.") per poll pair,
i.e. roughly two messages per second for as long as the input flaps (and up
to ~100 per second if the worker is also requeueing at 10 ms)?
Would dev_warn_ratelimited()/dev_info_ratelimited(), or a counter that a
transient re-lock does not clear, match the stated intent of surfacing
persistent failures "without flooding the log"?
quoted hunk ↗ jump to hunk
+ static void ice_ptp_periodic_work(struct kthread_work *work) { struct ice_ptp *ptp = container_of(work, struct ice_ptp, work.work);@@ -2858,6 +2922,8 @@ static void ice_ptp_periodic_work(struct kthread_work *work) if (pf->ptp.state != ICE_PTP_READY) return; + ice_ptp_tspll_monitor(pf); + err = ice_ptp_update_cached_phctime(pf);
[Severity: Low] The monitor runs ahead of the cached PHC refresh in the same serialized kthread work item. On a lock-lost tick it issues up to four sideband CGU transactions plus the unconditional usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC) in ice_tspll_restart_e825c(), and each control queue command is bounded only by ICE_CTL_Q_SQ_CMD_TIMEOUT (USEC_PER_SEC, "max delay 1s" in ice_controlq.h). ice_ptp_update_cached_phctime() documents that it "must be called periodically to ensure that the cached value is never more than 2 seconds old", and it warns and bumps late_cached_phc_updates when that is exceeded. Would calling ice_ptp_tspll_monitor() after ice_ptp_update_cached_phctime(), or from a separate work item, keep the sleeping hardware recovery sequence off the time-critical path?
quoted hunk ↗ jump to hunk
ice_ptp_maybe_trigger_tx_interrupt(pf);@@ -2975,6 +3041,9 @@ static int ice_ptp_rebuild_owner(struct ice_pf *pf) err = ice_tspll_init(hw); if (err) return err; + /* Rebuild reinitialized TSPLL, so reset monitor retry state. */ + WRITE_ONCE(ptp->tspll_locked, true); + ptp->tspll_lock_retries = 0; /* Acquire the global hardware lock */ if (!ice_ptp_lock(hw)) {@@ -3319,6 +3388,7 @@ void ice_ptp_init(struct ice_pf *pf) } ptp->port.port_num = hw->lane_num; + ptp->tspll_locked = true; ice_ptp_init_hw(hw);
[Severity: Low] Should ptp->tspll_lock_retries be cleared here as well? The rebuild path above resets both fields, but ice_ptp_init() only sets tspll_locked. struct ice_ptp is embedded in the probe-lifetime struct ice_pf, ice_ptp_release() does not clear either field, and ice_init_features()->ice_ptp_init() / ice_deinit_features()->ice_ptp_release() run again on the same pf across a devlink reload. If the TSPLL happened to be unlocked at unload time, does the leftover non-zero counter make the first successful poll after reload print "TimeSync PLL lock acquired with %s clock source after %u retries." for a lock that was never lost in this instance, and start the '% ICE_TSPLL_LOG_INTERVAL' gate at a stale phase so a genuine first lock-lost event stays unlogged for up to ~119 poll cycles?
quoted hunk ↗ jump to hunk
ice_ptp_init_tx_interrupt_mode(pf);diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h index c4b0da7ce20e..0e40fef3b4e8 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.h +++ b/drivers/net/ethernet/intel/ice/ice_ptp.h@@ -249,6 +249,15 @@ struct ice_ptp_pin_desc { * @tx_hwtstamp_discarded: number of Tx skbs discarded due to cached PHC time * being too old to correctly extend timestamp * @late_cached_phc_updates: number of times cached PHC update is late + * @tspll_locked: last observed TSPLL lock state on E825 owner PFs. + * Written by the PTP periodic worker after polling the TSPLL and + * intended to be read (without pf->dplls.lock) by the DPLL periodic + * worker in a follow-up change. Access via READ_ONCE()/WRITE_ONCE(); + * precise synchronization is not required because both workers + * converge on the same value within one poll period. + * @tspll_lock_retries: counts consecutive poll cycles in which the TSPLL + * was found unlocked. Reset to zero when lock is re-acquired. Used to + * rate-limit the lock-lost log message (~every 120 retries / ~60 s). */
[ ... ]
quoted hunk ↗ jump to hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_tspll.c b/drivers/net/ethernet/intel/ice/ice_tspll.c index fd4b58eb9bc0..78752dc1e762 100644 --- a/drivers/net/ethernet/intel/ice/ice_tspll.c +++ b/drivers/net/ethernet/intel/ice/ice_tspll.c
[ ... ]
quoted hunk ↗ jump to hunk
@@ -531,6 +531,62 @@ int ice_tspll_cfg_pps_out_e825c(struct ice_hw *hw, bool enable) return ice_write_cgu_reg(hw, ICE_CGU_R9, val); } +int ice_tspll_restart_e825c(struct ice_hw *hw) +{ + u32 val; + int err; + + /* Read the initial values of r23 and disable the PLL */ + err = ice_read_cgu_reg(hw, ICE_CGU_R23, &val); + if (err) + return err; + + val &= ~ICE_CGU_R23_R24_TSPLL_ENABLE; + err = ice_write_cgu_reg(hw, ICE_CGU_R23, val); + if (err) + return err; + + /* Wait at least 1 ms before reenabling PLL */ + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + val |= ICE_CGU_R23_R24_TSPLL_ENABLE; + return ice_write_cgu_reg(hw, ICE_CGU_R23, val); +}
[ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260908222428.872254-1-anthony.l.nguyen%40intel.com