From: sashiko-bot@kernel.org
To: "Jacob Keller" <jacob.e.keller@intel.com>,
"Intel Wired LAN" <intel-wired-lan@lists.osuosl.org>
Subject: Re: [PATCH iwl-net 2/2] ice: use struct timecounter for E825 and E830
Date: Sat, 24 Oct 2026 01:55:27 +0000 [thread overview]
Message-ID: <sashiko-outbox-183498@kernel.org> (raw)
In-Reply-To: <20261023-jk-e825-e830-timecounter-v1-2-e932602f0d16@intel.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] ice: 500ms offset before ice_phc_cyc2time() in ice_ptp_cfg_perout() wraps
- [High] ice: uncompensated timecounter offset for E830 TxTime in ice_tx_map()
- [Medium] ice: late PHC update check broken in ice_ptp_update_cached_phctime()
- [Medium] ice: missing timecounter conversion in ice_vc_get_phc_time()
- [Medium] ice: missing timecounter staleness and validity handling in ice_phc
- [Medium] ice: ice_ptp_prepare_for_reset() converts zero GLTSYN_TIME via timecounter
--
commit 348b48a49347fa2790d183338857035e4d8f1fa4
Author: Jacob Keller <jacob.e.keller@intel.com>
ice: use struct timecounter for E825 and E830
This commit switches E825 and E830 PTP hardware clock management from direct
hardware timer adjustments to a software struct timecounter and cyclecounter
embedded in struct ice_phc. This avoids PCS timer synchronization stalls on
E825 caused by manual PHY timer adjustments and converts timestamp extension,
clock reads, cross-timestamping, and periodic output configuration to account
for the timecounter phase offset.
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
> index 4b4a00beadd2c..488db8298059a 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
> @@ -210,6 +210,38 @@ void ice_ptp_restore_timestamp_mode(struct ice_pf *pf)
> ice_flush(hw);
> }
>
> +u64 ice_ptp_read_cycles(struct cyclecounter *cc)
> +{
> + struct ice_adapter *adapter;
> + struct ice_phc *phc;
> + struct ice_pf *pf;
> + struct ice_hw *hw;
> + u8 tmr_idx;
> +
> + phc = container_of(cc, struct ice_phc, cc);
> + adapter = container_of(phc, struct ice_adapter, phc);
> +
> + scoped_guard(rcu) {
> + pf = rcu_dereference(adapter->ctrl_pf);
> + if (!pf)
> + return 0;
[ ... ]
> @@ -995,19 +1046,17 @@ static int ice_ptp_init_tx(struct ice_pf *pf, struct ice_ptp_tx *tx, u8 port)
> * ice_ptp_update_cached_phctime - Update the cached PHC time value
> * @pf: Board specific private structure
> *
> - * Update the PTP hardware clock time values which are cached in the PHC
> - * structure.
> + * For devices that use a timecounter structure, update the timecounter.
> + * Ohterwise, update the cached PHC time used for timestamp extension.
> *
> * This function must be called periodically by the clock owner to ensure that
> - * the cached value is never more than 2 seconds old.
> + * the timecounter or cached value is never more than 2 seconds old.
> */
> static void ice_ptp_update_cached_phctime(struct ice_pf *pf)
> {
> struct ice_phc *phc = &pf->adapter->phc;
> struct device *dev = ice_pf_to_dev(pf);
> unsigned long update_before;
> - u64 systime;
> - int err;
>
> update_before = phc->update_jiffies + msecs_to_jiffies(2000);
> if (phc->cached_time && time_is_before_jiffies(update_before)) {
> @@ -1018,21 +1067,33 @@ static void ice_ptp_update_cached_phctime(struct ice_pf *pf)
> pf->ptp.late_cached_phc_updates++;
> }
>
> - /* Read the current PHC time */
> - err = ice_ptp_read_src_clk_reg(pf, NULL, &systime);
> - if (err) {
> - /* The source clock is gone, so the cache can no longer be
> - * refreshed. Publish zero instead of leaving the last good
> - * value behind: neither ice_ptp_get_rx_hwts() nor
> - * ice_ptp_extend_40b_ts() may extend against a value that no
> - * longer tracks the PHC, and both treat a zero cache as
> - * "no timestamp".
> - */
> - WRITE_ONCE(phc->cached_time, 0);
> + WRITE_ONCE(phc->update_jiffies, jiffies);
> +
> + if (phc->use_timecounter) {
> + unsigned long flags;
> +
> + write_seqlock_irqsave(&phc->tc_lock, flags);
> + timecounter_read(&phc->tc);
> + write_sequnlock_irqrestore(&phc->tc_lock, flags);
> } else {
> - /* Update the cached PHC time stored in the PHC structure */
> + u64 systime;
> + int err;
> +
> + err = ice_ptp_read_src_clk_reg(pf, NULL, &systime);
> + if (err) {
> + /* The source clock is gone, so the cache can no
> + * longer be refreshed. Publish zero instead of
> + * leaving the last good value behind: neither
> + * ice_ptp_get_rx_hwts() nor ice_ptp_extend_40b_ts()
> + * may extend against a value that no longer tracks
> + * the PHC, and both treat a zero cache as "no
> + * timestamp".
> + */
> + systime = 0;
> + }
> +
> + /* Update the cached PHC time stored in the PF structure */
> WRITE_ONCE(phc->cached_time, systime);
> - WRITE_ONCE(phc->update_jiffies, jiffies);
> }
> }
[Severity: Medium]
In ice_ptp_update_cached_phctime(), the check for late updates still tests
phc->cached_time:
update_before = phc->update_jiffies + msecs_to_jiffies(2000);
if (phc->cached_time && time_is_before_jiffies(update_before)) {
unsigned long time_taken = jiffies - phc->update_jiffies;
dev_warn(dev, "%u msecs passed between update to cached PHC time\n",
jiffies_to_msecs(time_taken));
pf->ptp.late_cached_phc_updates++;
}
When phc->use_timecounter is true on E825 and E830 devices, phc->cached_time
is initialized to 0 in ice_adapter_new() and is never written. Won't this
cause the condition to always evaluate to false in timecounter mode,
preventing late timecounter updates from ever emitting the warning or
incrementing pf->ptp.late_cached_phc_updates?
Also, by moving WRITE_ONCE(phc->update_jiffies, jiffies) above the
if (phc->use_timecounter) branch, phc->update_jiffies is now updated on
legacy devices even when ice_ptp_read_src_clk_reg() fails and systime is set
to 0. Should phc->update_jiffies only be updated when the clock read
succeeds, and should the late-update check also cover timecounter mode?
[ ... ]
> @@ -1793,8 +1859,9 @@ static int ice_ptp_write_perout(struct ice_hw *hw, unsigned int chan,
> static int ice_ptp_cfg_perout(struct ice_pf *pf, struct ptp_perout_request *rq,
> int on)
> {
> + struct ice_phc *phc = &pf->adapter->phc;
> + u64 clk, period, start, phase, cycles;
> unsigned int gpio_pin, prop_delay_ns;
> - u64 clk, period, start, phase;
> struct ice_hw *hw = &pf->hw;
> int pin_desc_idx;
> int err;
> @@ -1840,12 +1907,21 @@ static int ice_ptp_cfg_perout(struct ice_pf *pf, struct ptp_perout_request *rq,
> if (err)
> return err;
> clk += NSEC_PER_MSEC * 500;
> + if (phc->use_timecounter) {
> + cycles = clk;
> + clk = ice_phc_cyc2time(phc, cycles);
> + }
> +
> if (rq->flags & PTP_PEROUT_PHASE || start <= clk - prop_delay_ns)
> start = div64_u64(clk + period - 1, period) * period + phase;
>
> /* Compensate for propagation delay from the generator to the pin. */
> start -= prop_delay_ns;
>
> + /* Compensate for timecounter phase offset, if necessary */
> + if (phc->use_timecounter)
> + start -= (clk - cycles);
> +
> return ice_ptp_write_perout(hw, rq->index, gpio_pin, start, period);
> }
[Severity: High]
In ice_ptp_cfg_perout(), 500 ms (NSEC_PER_MSEC * 500) is added to the raw
hardware clock value before passing cycles to ice_phc_cyc2time():
err = ice_ptp_read_src_clk_reg(pf, NULL, &clk);
if (err)
return err;
clk += NSEC_PER_MSEC * 500;
if (phc->use_timecounter) {
cycles = clk;
clk = ice_phc_cyc2time(phc, cycles);
}
Since phc->cc uses CYCLECOUNTER_MASK(32), timecounter_cyc2time() treats any
cycle value where (cycles - tc->cycle_last) & 0xffffffff exceeds
cc->mask / 2 (~2.147 seconds) as having occurred in the past, subtracting the
delta from tc->nsec instead of adding it.
Because cycles is already 0.5 seconds ahead of the current hardware counter,
doesn't this shrink the forward window before wraparound from ~2.147 seconds
to ~1.647 seconds? If tc->cycle_last was updated between 1.648 and 2.0
seconds ago (within the normal periodic update window, or during
ice_ptp_adjtime_tc() -> ice_ptp_enable_all_perout() where tc->cycle_last is
not refreshed), ice_phc_cyc2time() will wrap backwards by 2^32 ns, making
(clk - cycles) and the resulting GLTSYN_TGT_L/H start time off by 2^32 ns
(and shifting the periodic output phase by 2^32 mod period).
Should ice_phc_cyc2time() be called on the raw clk value before adding
NSEC_PER_MSEC * 500?
[ ... ]
> @@ -2414,15 +2546,9 @@ u64 ice_ptp_get_rx_hwts(const union ice_32b_rx_flex_desc *rx_desc,
> return 0;
>
> cached_time = READ_ONCE(pkt_ctx->phc->cached_time);
> - if (!cached_time)
> + if (!pkt_ctx->phc->use_timecounter && !cached_time)
> return 0;
>
> - /* Use ice_ptp_extend_32b_ts directly, using the ring-specific cached
> - * PHC value, rather than accessing the PF. This also allows us to
> - * simply pass the upper 32bits of nanoseconds directly. Calling
> - * ice_ptp_extend_40b_ts is unnecessary as it would just discard these
> - * bits itself.
> - */
> ts_high = le32_to_cpu(rx_desc->wb.flex_ts.ts_high);
> ts_ns = ice_ptp_extend_32b_ts(pkt_ctx->phc, ts_high);
[Severity: Medium]
When pkt_ctx->phc->use_timecounter is true, ice_ptp_get_rx_hwts() bypasses
the cached_time check and does not check phc->update_jiffies (unlike
ice_ptp_extend_40b_ts()) or whether the clock owner is active.
Because timecounter_cyc2time() with a 32-bit cyclecounter is only valid
within +/- 2.14 seconds of tc->cycle_last, what happens if the periodic work
is delayed past 2 seconds, or if the clock owner PF on a multi-PF adapter is
unbound in ice_ptp_release(), fails ice_ptp_rebuild(), or fails
ice_ptp_init_owner() after setting phc->use_timecounter = 1? Won't sibling
PFs continue extending Rx timestamps against a frozen or uninitialized
phc->tc instead of returning 0?
Relatedly, in ice_ptp_release(), ice_ptp_cleanup_adapter() clears
adapter->ctrl_pf before cancelling pf->ptp.work and unregistering the PTP
clock, which allows ice_ptp_read_cycles() to observe a NULL ctrl_pf and
return 0 as a valid cycle count into timecounter_read() or
timecounter_init(). Also, ice_ptp_init_owner(), ice_ptp_rebuild_owner(), and
ice_ptp_settime64_tc() call timecounter_init() without refreshing
phc->update_jiffies, which can leave phc->update_jiffies stale across a reset
until the periodic work runs and cause ice_ptp_extend_40b_ts() to discard
valid Tx timestamps right after ice_ptp_restore_timestamp_mode().
[ ... ]
> @@ -3133,8 +3266,11 @@ void ice_ptp_prepare_for_reset(struct ice_pf *pf, enum ice_reset_req reset_type)
>
> /* Acquire PHC and system timer to restore after reset */
> ptp->reset_time = ktime_get_real_ns();
> - if (ice_ptp_read_src_clk_reg(pf, NULL, &ptp->phc_reset_time))
> - ptp->phc_reset_time = 0;
> + if (ice_ptp_read_src_clk_reg(pf, NULL, &ns))
> + ns = 0;
> + else if (pf->adapter->phc.use_timecounter)
> + ns = ice_phc_cyc2time(&pf->adapter->phc, ns);
> + ptp->phc_reset_time = ns;
> }
[Severity: Medium]
When a hardware reset (such as CORER, GLOBR, or EMPR) has already cleared the
GLTSYN_TIME registers to 0 before ice_ptp_prepare_for_reset() runs,
ice_ptp_read_src_clk_reg() on the clock owner PF returns 0 with ns = 0.
Previously, ns = 0 resulted in ptp->phc_reset_time = 0, causing
ice_ptp_rebuild_owner() to fall back to ktime_get_real_ns():
if (ptp->phc_reset_time) {
time_diff = ktime_get_real_ns() - ptp->reset_time;
ns = ptp->phc_reset_time + time_diff;
} else {
ns = ktime_get_real_ns();
}
With phc.use_timecounter enabled, the else-if branch passes ns = 0 to
ice_phc_cyc2time(&pf->adapter->phc, 0), which computes a 32-bit delta between
cycle 0 and the pre-reset tc->cycle_last. Doesn't this turn a zeroed hardware
register into a non-zero ptp->phc_reset_time shifted by up to +/- 2.147
seconds, bypassing the ktime_get_real_ns() fallback in
ice_ptp_rebuild_owner()?
[ ... ]
> @@ -3375,10 +3522,21 @@ int ice_ptp_clock_index(struct ice_pf *pf)
> */
> static int ice_ptp_init_owner(struct ice_pf *pf)
> {
> + struct ice_phc *phc = &pf->adapter->phc;
> struct ice_hw *hw = &pf->hw;
> - struct timespec64 ts;
> int err;
>
> + switch (hw->mac_type) {
> + case ICE_MAC_GENERIC_3K_E825:
> + case ICE_MAC_E830:
> + /* Use struct timecounter for E825 and E830 devices */
> + dev_dbg(ice_pf_to_dev(pf), "PTP 1588 enabling timecounter mode\n");
> + phc->use_timecounter = 1;
> + break;
> + default:
> + break;
> + }
[Severity: High]
Enabling phc->use_timecounter on ICE_MAC_E830 means the hardware
E830_GLTSYN_TIME registers are no longer initialized in ice_ptp_init_owner()
or ice_ptp_rebuild_owner(), nor adjusted in ice_ptp_settime64_tc() and
ice_ptp_adjtime_tc().
However, E830 supports ICE_F_TXTIME hardware offload:
drivers/net/ethernet/intel/ice/ice_lib.c:ice_init_feature_support() {
if (pf->hw.mac_type == ICE_MAC_E830) {
ice_set_feature_support(pf, ICE_F_MBX_LIMIT);
ice_set_feature_support(pf, ICE_F_GCS);
ice_set_feature_support(pf, ICE_F_TXTIME);
}
}
And in ice_tx_map(), the TxTime descriptor timestamp is populated directly
from first->skb->tstamp without compensating for the timecounter phase
offset:
drivers/net/ethernet/intel/ice/ice_txrx.c:ice_tx_map() {
ts = ktime_to_timespec64(first->skb->tstamp);
tstamp = ts.tv_nsec >> ICE_TXTIME_CTX_RESOLUTION_128NS;
ts_desc = ICE_TS_DESC(tstamp_ring, j);
ts_desc->tx_desc_idx_tstamp = ice_build_tstamp_desc(i, tstamp);
}
How does ICE_F_TXTIME work on E830 when the PHC time is maintained in the
software timecounter while the hardware TxTime queue compares descriptor
timestamps against the unadjusted hardware clock?
[Severity: Medium]
Similarly, when an SR-IOV VF requests the PHC time via
VIRTCHNL_OP_1588_PTP_GET_TIME, ice_vc_get_phc_time() in
drivers/net/ethernet/intel/ice/virt/virtchnl.c still reads the raw hardware
register without converting through ice_phc_cyc2time():
drivers/net/ethernet/intel/ice/virt/virtchnl.c:ice_vc_get_phc_time() {
if (ice_ptp_read_src_clk_reg(pf, NULL, &phc_time->time)) {
v_ret = VIRTCHNL_STATUS_ERR_NOT_SUPPORTED;
len = 0;
}
}
Furthermore, in the VF driver, iavf_ptp_extend_32b_timestamp() in
drivers/net/ethernet/intel/iavf/iavf_ptp.c computes:
u32 low = lower_32_bits(cached_phc_time);
u32 delta = in_tstamp - low;
which assumes the lower 32 bits of the PHC time returned by the PF match the
raw 32-bit hardware timestamp domain in the Rx descriptor. Won't enabling
phc->use_timecounter on E825 and E830 break both VF PTP clock reads and VF Rx
hardware timestamp extension?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261023-jk-e825-e830-timecounter-v1-0-e932602f0d16@intel.com?part=2
next prev parent reply other threads:[~2026-10-24 1:55 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-24 1:24 [PATCH iwl-net 0/2] ice: E825 timer adjustment fixes Jacob Keller
2026-10-24 1:24 ` [PATCH iwl-net 1/2] ice: store cached PHC time in ice_adapter Jacob Keller
2026-10-24 1:46 ` sashiko-bot
2026-10-26 23:46 ` Jacob Keller
2026-10-24 1:24 ` [PATCH iwl-net 2/2] ice: use struct timecounter for E825 and E830 Jacob Keller
2026-10-24 1:55 ` sashiko-bot [this message]
2026-10-27 19:21 ` Jacob Keller
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-183498@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=intel-wired-lan@lists.osuosl.org \
--cc=jacob.e.keller@intel.com \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.