From: Jacob Keller <jacob.e.keller@intel.com>
To: <sashiko-reviews@lists.linux.dev>,
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: Tue, 27 Oct 2026 12:21:14 -0700 [thread overview]
Message-ID: <cbee478d-c729-422a-8f52-117de23f2c12@intel.com> (raw)
In-Reply-To: <sashiko-outbox-183498@kernel.org>
On 10/23/2026 6:55 PM, sashiko-bot@kernel.org wrote:
> 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?
>
Yea, this makes sense, I think I can re-order things to make that work.
> [ ... ]
>
>> @@ -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?
>
Hm. Ya that seems correct. Can fix.
> [ ... ]
>
>> @@ -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?
>
True. We weren't checking jiffies before because it wasn't accessible
and partially because it was considered expensive and not worth doing
for the per-packet Rx timestamps.
We can fix the
> 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().
Strictly, i don't think I had really intended the jiffies protection to
be applied to the timecounter logic. But I can fix that. Additionally,
ice_ptp_read_cycles() is actually only ever called from the periodic
work (all other callers should be using timecounter_cyc2time to avoid
modifying the timecounter unnecessarily and causing the seqlock contention).
I can try to rework this to fix that.
>
> [ ... ]
>
>> @@ -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.
>
I am not certain this is actually possible, I would have to check if
those resets actually do clear these registers.
> 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()?
>
I'll try to fix the logic regardless.
> [ ... ]
>
>> @@ -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?
>
This may be somewhat tricky. We need to compute the inverse of the
timecounter phase like we do with periodic output.
> [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?
>
We can use ice_phc_cyc2time to convert the time reported to the VF, but
then the raw timestamps captured won't convert properly with timestamp
extension (they lack access to the timecounter). I think we need to
disable Rx timestamp offload until we figure out how to resolve this.
prev parent reply other threads:[~2026-10-27 19:22 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
2026-10-27 19:21 ` Jacob Keller [this message]
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=cbee478d-c729-422a-8f52-117de23f2c12@intel.com \
--to=jacob.e.keller@intel.com \
--cc=intel-wired-lan@lists.osuosl.org \
--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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox