* [RFC PATCH 0/1] timekeeping: Don't charge NTP frequency changes to ntp_error
@ 2026-11-05 3:21 Yuan Wang
2026-11-05 3:21 ` [RFC PATCH 1/1] " Yuan Wang
[not found] ` <cd23df8dde221a908233b5dfde548383991b4537.camel@amazon.co.uk>
0 siblings, 2 replies; 3+ messages in thread
From: Yuan Wang @ 2026-11-05 3:21 UTC (permalink / raw)
To: David Woodhouse, Miroslav Lichvar, John Stultz, Thomas Gleixner
Cc: jie.wang, yifan2.li, junxiao.chang, Stephen Boyd,
Richard Cochran, linux-kernel
From: Jie Wang <jie.wang@intel.com>
Hi,
Since b7befd6d9120 ("timekeeping: Account for monotonicity adjustment in
ntp_error") we see phc2sys take minutes, instead of seconds, to settle
CLOCK_REALTIME back to a few tens of ns after the PHC it follows is
stepped. The PHC itself (ptp4l) recovers as fast as before; only the
PHC -> CLOCK_REALTIME loop is affected.
Setup: two Intel i226 NICs back to back, gPTP (802.1AS profile, L2 P2P),
ptp4l + "phc2sys -O 0 -c CLOCK_REALTIME -s <if>" on both ends, PREEMPT_RT,
HZ=1000, TSC clocksource. The grandmaster's PHC is stepped by +1s with
"testptp -t 1". Time until |phc2sys offset| stays below 100ns:
GM phc2sys slave phc2sys slave ptp4l
v7.0 (before the series) 51-56s (test team, slave side)
v7.2.7 (b7befd6d9120 only) 257-386s 79-333s 33-34s
v7.2.7 + revert of b7befd6d9120 32s 40-42s 34-35s
v7.3 (whole series) 207/>361/>413s 79-293s 34-35s
v7.3 + revert of the series 32s 40-41s 34-35s
v7.3 + this patch 32s 40-41s 34-35s
(">" = not settled before the next step 400s later)
What happens: after the step phc2sys slews CLOCK_REALTIME at up to 10%
(ADJ_TICK + ADJ_FREQUENCY) and then backs off by several percent per
second. Since b7befd6d9120 every one of these base-rate changes adds
offset * mult_adj to ntp_error - up to a tick's worth of cycles times the
frequency change, i.e. tens of microseconds per update. ntp_error is only
repaid by the +1 mult dither (a few hundred ppb here), so CLOCK_REALTIME
keeps moving on its own for minutes after phc2sys has converged, and the
servo keeps chasing it.
That correction is the one c2cda2a5bda9 ("timekeeping/ntp: Don't align
NTP frequency adjustments to ticks") removed in 2018: it makes the clock
behave as if a new frequency had been applied at the start of the tick
rather than when adjtimex() was called. As far as I can tell the v7 series
discussion didn't revisit that trade-off.
I think both views are right for different callers:
- A new base rate set from user space (ADJ_FREQUENCY/ADJ_TICK) belongs to
an external servo (chrony, ptp4l, phc2sys) that measures the clock
directly. Any phase effect is seen and corrected by that servo; booking
it in ntp_error as well makes the kernel pay it back a second time,
slowly.
- The kernel's own adjustments - the mult/mult+1 dither and the
skew_delta bias that delivers time_offset/time_adjust - have a phase
target inside the kernel, and there the compensation from b7befd6d9120
is what keeps the delivered phase exact.
So the patch keeps the compensation for the dither and skew changes, but
applies a new NTP base rate without charging ntp_error, as before
b7befd6d9120.
Open question, mainly for David: if timekeeping_set_reference() (ffclock
branch) changes tick_length, it would now also be applied "at the time of
the update". If that path needs the as-if-at-tick semantics, I'd suggest
it delivers the phase through time_offset (which the series now makes
exact) or passes an explicit flag down to timekeeping_adjust(); happy to
rework the patch either way.
The patch doesn't change the kernel-internal slews. An adjtime()
(ADJ_OFFSET_SINGLESHOT) of +5000us with no servo running, measured as
CLOCK_REALTIME - CLOCK_MONOTONIC_RAW, settles 10-12s later at:
v7.3: 5009255ns (+9.3us)
v7.3 + this patch: 5001024ns (+1.0us)
(one run each, so I'd read that as "not worse" rather than "better"; the
residual error in both cases looks worth a separate look). Steady-state
phc2sys lock before the step is the same on all three 7.3 kernels
(7-10ns rms on the GM).
phc2sys -S (step instead of slew) avoids the problem on an unpatched
kernel - a step clears ntp_error - but steps are usually not acceptable
on TSN systems once the schedule is running, so that is only a
workaround.
This fixes a 7.2 regression, so it probably wants
Cc stable list once the approach is agreed.
Jie Wang (1):
timekeeping: Don't charge NTP frequency changes to ntp_error
kernel/time/timekeeping.c | 26 +++++++++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)
base-commit: c03c1a14507dfaf8b856c2608f9ac216021fc222
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [RFC PATCH 1/1] timekeeping: Don't charge NTP frequency changes to ntp_error 2026-11-05 3:21 [RFC PATCH 0/1] timekeeping: Don't charge NTP frequency changes to ntp_error Yuan Wang @ 2026-11-05 3:21 ` Yuan Wang [not found] ` <cd23df8dde221a908233b5dfde548383991b4537.camel@amazon.co.uk> 1 sibling, 0 replies; 3+ messages in thread From: Yuan Wang @ 2026-11-05 3:21 UTC (permalink / raw) To: David Woodhouse, Miroslav Lichvar, John Stultz, Thomas Gleixner Cc: jie.wang, yifan2.li, junxiao.chang, Stephen Boyd, Richard Cochran, linux-kernel From: Jie Wang <jie.wang@intel.com> Commit b7befd6d9120 ("timekeeping: Account for monotonicity adjustment in ntp_error") adds the xtime_nsec monotonicity fixup to ntp_error for every mult change, including the change of the base rate when user space sets a new frequency via adjtimex(). That makes the clock behave as if the new frequency had been applied at the start of the tick, which is exactly the correction removed by commit c2cda2a5bda9 ("timekeeping/ntp: Don't align NTP frequency adjustments to ticks"). The amount is offset * mult_adj, i.e. up to one tick worth of cycles times the frequency change. After a clock step, phc2sys and ptp4l slew CLOCK_REALTIME at up to 10% and then back off by several percent per second, which charges tens of microseconds to ntp_error each second. ntp_error is only repaid by the +1 mult dithering (tens to hundreds of ppb), so the clock keeps drifting on its own for minutes and fights the external servo. On two Intel i226 NICs running gPTP (ptp4l + phc2sys on each end, PREEMPT_RT, HZ=1000, TSC), after a 1s step of the grandmaster's PHC, the time until the phc2sys offset stays below 100ns is: GM phc2sys slave phc2sys slave ptp4l v7.3 207/>361/>413s 79-293s 34-35s v7.3, series reverted 32s 40-41s 34-35s v7.3 + this patch 32s 40-41s 34-35s ">" means it had not settled before the next step, 400s later. Keep the ntp_error compensation for the ntp_err_mult dithering and the skew bias, but apply a new base rate without charging ntp_error, so a frequency change takes effect at the time of the update again. Fixes: b7befd6d9120 ("timekeeping: Account for monotonicity adjustment in ntp_error") Signed-off-by: Jie Wang <jie.wang@intel.com> --- kernel/time/timekeeping.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index ea2e6e55f37b..8f6ab6c077f0 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -2347,7 +2347,8 @@ device_initcall(timekeeping_init_ops); */ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk, s64 offset, - s32 mult_adj) + s32 mult_adj, + bool track_error) { s64 interval = tk->cycle_interval; @@ -2412,6 +2413,10 @@ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk, * (in appropriate units) has to be added to ntp_error, in * order to correctly track the delta between the time * reported in xtime_nsec, and the intended time. + * + * The exception is a new NTP frequency (@track_error == false): + * it takes effect at the time of this update, not at the start + * of the tick, so there is no delta to track. */ if ((mult_adj > 0) && (tk->tkr_mono.mult + mult_adj < mult_adj)) { /* NTP adjustment caused clocksource mult overflow */ @@ -2422,7 +2427,8 @@ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk, tk->tkr_mono.mult += mult_adj; tk->xtime_interval += interval; tk->tkr_mono.xtime_nsec -= offset; - tk->ntp_error += offset << tk->ntp_error_shift; + if (track_error) + tk->ntp_error += offset << tk->ntp_error_shift; } /* @@ -2433,6 +2439,7 @@ static void timekeeping_adjust(struct timekeeper *tk, s64 offset) { u64 ntp_tl = ntp_tick_length(tk->id); s64 skew = ntp_get_skew_delta(tk->id); + bool freq_changed = tk->ntp_tick != ntp_tl; u32 mult; /* @@ -2453,6 +2460,19 @@ static void timekeeping_adjust(struct timekeeper *tk, s64 offset) skew *= NTP_INTERVAL_FREQ; mult = div64_u64((tk->ntp_tick + skew) >> tk->ntp_error_shift, tk->cycle_interval); + + /* + * Switch to the new base rate first. A new NTP frequency takes + * effect at the time of this update rather than at the start of + * the tick, so don't charge the realignment to ntp_error. + * ntp_error is only repaid by the +1 mult dithering below, so + * after a large frequency change (e.g. phc2sys/ptp4l slewing + * after a clock step) it would otherwise drag the clock for + * minutes against the external servo. + */ + timekeeping_apply_adjustment(tk, offset, + mult + tk->ntp_err_mult - tk->tkr_mono.mult, + !freq_changed); } /* @@ -2464,7 +2484,7 @@ static void timekeeping_adjust(struct timekeeper *tk, s64 offset) tk->ntp_err_mult = tk->ntp_error > 0 ? 1 : 0; mult += tk->ntp_err_mult; - timekeeping_apply_adjustment(tk, offset, mult - tk->tkr_mono.mult); + timekeeping_apply_adjustment(tk, offset, mult - tk->tkr_mono.mult, true); if (unlikely(tk->tkr_mono.clock->maxadj && (abs(tk->tkr_mono.mult - tk->tkr_mono.clock->mult) -- 2.43.0 ^ permalink raw reply [flat|nested] 3+ messages in thread
[parent not found: <cd23df8dde221a908233b5dfde548383991b4537.camel@amazon.co.uk>]
* Re: [RFC PATCH 0/1] timekeeping: Don't charge NTP frequency changes to ntp_error [not found] ` <cd23df8dde221a908233b5dfde548383991b4537.camel@amazon.co.uk> @ 2026-11-05 16:29 ` David Woodhouse 0 siblings, 0 replies; 3+ messages in thread From: David Woodhouse @ 2026-11-05 16:29 UTC (permalink / raw) To: Yuan Wang, Miroslav Lichvar, John Stultz, Thomas Gleixner Cc: jie.wang, yifan2.li, junxiao.chang, Stephen Boyd, Richard Cochran, linux-kernel [-- Attachment #1: Type: text/plain, Size: 1233 bytes --] On Wed, 2026-11-04 at 20:29 -0800, Woodhouse, David wrote: > On Thu, 2026-11-05 at 11:21 +0800, Yuan Wang wrote: > > > > Open question, mainly for David: if timekeeping_set_reference() (ffclock > > branch) changes tick_length, it would now also be applied "at the time of > > the update". If that path needs the as-if-at-tick semantics, I'd suggest > > it delivers the phase through time_offset (which the series now makes > > exact) or passes an explicit flag down to timekeeping_adjust(); happy to > > rework the patch either way. > > Yeah, timekeeping_set_reference() doesn't care. It's going to reset > ntp_error *after* changing the tick_length anyway. > > I think your patch is functionally identical to the one I've already > posted in > https://lore.kernel.org/all/20261001202134.33929-2-dwmw2@infradead.org/ ? Not *quite* functionally identical; I think your version misses the ntp_error updates that we *should* be doing, when tick_length changes while a skew is active? Mine compensates only for the tick_length part, which I think is correct? I'm due to send v2 of that series; please could you test that, and I'll add the appropriate Fixes: and Cc: stable with your Tested-by:. Thanks. [-- Attachment #2: smime.p7s --] [-- Type: application/pkcs7-signature, Size: 6179 bytes --] ^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-11-05 16:29 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-05 3:21 [RFC PATCH 0/1] timekeeping: Don't charge NTP frequency changes to ntp_error Yuan Wang
2026-11-05 3:21 ` [RFC PATCH 1/1] " Yuan Wang
[not found] ` <cd23df8dde221a908233b5dfde548383991b4537.camel@amazon.co.uk>
2026-11-05 16:29 ` [RFC PATCH 0/1] " David Woodhouse
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®