From: David Woodhouse <dwmw2@infradead.org>
To: Thomas Gleixner <tglx@kernel.org>, John Stultz <jstultz@google.com>
Cc: Stephen Boyd <sboyd@kernel.org>,
Miroslav Lichvar <mlichvar@redhat.com>,
Rodolfo Giometti <giometti@enneenne.com>,
Ryan Luu <rluu@amazon.com>, Julien Ridoux <ridouxj@amazon.com>,
linux-kernel@vger.kernel.org,
David Woodhouse <dwmw2@infradead.org>,
David Woodhouse <dwmw@amazon.co.uk>
Subject: [PATCH 2/5] ntp: Recalculate skew_delta when the phase offset changes
Date: Thu, 1 Oct 2026 21:21:31 +0100 [thread overview]
Message-ID: <20261001202134.33929-3-dwmw2@infradead.org> (raw)
In-Reply-To: <20261001202134.33929-1-dwmw2@infradead.org>
From: David Woodhouse <dwmw@amazon.co.uk>
The skew rate is recomputed only by second_overflow(), so when
time_offset or time_adjust changes mid-second (hardpps on each pulse,
adjtimex, adjtime()) the stale rate keeps delivering until the end of
the second, and the misdelivery lands in ntp_error. Pull the
computation out into ntp_update_skew_delta() and call it from the
writers. Settling of an opposing time_offset/time_adjust overlap stays
paced at one chunk per second, via the settle argument.
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: LLM
---
kernel/time/ntp.c | 144 +++++++++++++++++++++++++++-------------------
1 file changed, 85 insertions(+), 59 deletions(-)
diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index d22b532ec536..b8b0bf1d94e7 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -593,6 +593,82 @@ ktime_t ntp_get_next_leap(unsigned int tkid)
return KTIME_MAX;
}
+/*
+ * ntp_update_skew_delta - Recompute the per-tick skew rate for the
+ * current second from the pending time_offset / time_adjust phase.
+ *
+ * The rate is in the same units as time_offset: (ns << NTP_SCALE_SHIFT)
+ * / HZ. If the result is so low that the skew imparted would round to
+ * zero, pass the bare minimum +-1 to ensure that it *does* actually
+ * drain completely to zero. It won't overshoot because
+ * logarithmic_accumulation() only drains what it can from time_offset
+ * or time_adjust, and the rest ends up in ntp_error which drives the
+ * selection of 'mult' immediately each tick.
+ *
+ * Called from second_overflow() at each second boundary (@settle =
+ * true), and directly by the writers of time_offset / time_adjust so a
+ * change takes effect at the next timekeeping advance rather than
+ * persisting a stale rate until the end of the second. @settle gates
+ * ntp_transfer_offset_adjust(): settling the opposing overlap is paced
+ * at one chunk per second by design and is the only mutation here, so
+ * with @settle false the call is a pure recompute, safe at any rate.
+ *
+ * The timekeeper lock is held on all paths, serializing ntp_data.
+ */
+static void ntp_update_skew_delta(struct ntp_data *ntpdata, bool settle)
+{
+ if (ntpdata->time_offset || ntpdata->time_adjust ||
+ ntpdata->time_adjust_frac) {
+ s64 off_chunk = ntp_offset_chunk(ntpdata, ntpdata->time_offset);
+ s64 adj_chunk = 0, net;
+
+ /*
+ * Once the exponential chunk rounds to zero, deliver the last
+ * remaining offset this second so it converges to zero instead
+ * of stalling just above it.
+ */
+ if (!off_chunk)
+ off_chunk = ntpdata->time_offset;
+
+ if (ntpdata->time_adjust || ntpdata->time_adjust_frac) {
+ s64 adj;
+
+ if (ntpdata->time_adjust >= MAX_TICKADJ)
+ adj = MAX_TICKADJ * ONE_US_NS;
+ else if (ntpdata->time_adjust <= -MAX_TICKADJ)
+ adj = -MAX_TICKADJ * ONE_US_NS;
+ else
+ adj = ntpdata->time_adjust * ONE_US_NS +
+ ntpdata->time_adjust_frac;
+
+ adj_chunk = div_s64(adj, NTP_INTERVAL_FREQ);
+ if (!adj_chunk)
+ adj_chunk = signof(ntpdata->time_adjust_frac);
+ }
+
+ /*
+ * If the two slews oppose, only their net would drive the
+ * per-tick drain, so the cancelling part would never drain from
+ * either tracker and an exact cancellation would stall both.
+ * Settle that overlap directly between them (no clock motion).
+ */
+ if (settle && off_chunk && adj_chunk &&
+ signof(off_chunk) != signof(adj_chunk)) {
+ s64 conflict = min(abs(off_chunk), abs(adj_chunk));
+
+ ntp_transfer_offset_adjust(ntpdata, signof(off_chunk) * conflict);
+ }
+
+ /* Net is what the clock delivers; reduce to per-tick, then floor. */
+ net = off_chunk + adj_chunk;
+ ntpdata->skew_delta = div_s64(net, NTP_INTERVAL_FREQ);
+ if (!ntpdata->skew_delta && net)
+ ntpdata->skew_delta = signof(net);
+ } else {
+ ntpdata->skew_delta = 0;
+ }
+}
+
/*
* This routine handles the overflow of the microsecond field
*
@@ -669,65 +745,8 @@ int second_overflow(unsigned int tkid, time64_t secs)
/* Check PPS signal */
pps_dec_valid(ntpdata);
- /*
- * Set the per-tick skew rate for the next second. This is in
- * the same units as time_offset: (ns << NTP_SCALE_SHIFT) / HZ.
- * If the result is so low that the skew imparted would round
- * to zero, pass the bare minimum ±1 to ensure that it *does*
- * actually drain completely to zero. It won't overshoot because
- * logarithmic_accumulation() only drains what it can from
- * time_offset or time_adjust, and the rest ends up in ntp_error
- * which drives the selection of 'mult' immediately each tick.
- */
- if (ntpdata->time_offset || ntpdata->time_adjust ||
- ntpdata->time_adjust_frac) {
- s64 off_chunk = ntp_offset_chunk(ntpdata, ntpdata->time_offset);
- s64 adj_chunk = 0, net;
-
- /*
- * Once the exponential chunk rounds to zero, deliver the last
- * remaining offset this second so it converges to zero instead
- * of stalling just above it.
- */
- if (!off_chunk)
- off_chunk = ntpdata->time_offset;
-
- if (ntpdata->time_adjust || ntpdata->time_adjust_frac) {
- s64 adj;
-
- if (ntpdata->time_adjust >= MAX_TICKADJ)
- adj = MAX_TICKADJ * ONE_US_NS;
- else if (ntpdata->time_adjust <= -MAX_TICKADJ)
- adj = -MAX_TICKADJ * ONE_US_NS;
- else
- adj = ntpdata->time_adjust * ONE_US_NS +
- ntpdata->time_adjust_frac;
-
- adj_chunk = div_s64(adj, NTP_INTERVAL_FREQ);
- if (!adj_chunk)
- adj_chunk = signof(ntpdata->time_adjust_frac);
- }
-
- /*
- * If the two slews oppose, only their net would drive the
- * per-tick drain, so the cancelling part would never drain from
- * either tracker and an exact cancellation would stall both.
- * Settle that overlap directly between them (no clock motion).
- */
- if (off_chunk && adj_chunk && signof(off_chunk) != signof(adj_chunk)) {
- s64 conflict = min(abs(off_chunk), abs(adj_chunk));
-
- ntp_transfer_offset_adjust(ntpdata, signof(off_chunk) * conflict);
- }
-
- /* Net is what the clock delivers; reduce to per-tick, then floor. */
- net = off_chunk + adj_chunk;
- ntpdata->skew_delta = div_s64(net, NTP_INTERVAL_FREQ);
- if (!ntpdata->skew_delta && net)
- ntpdata->skew_delta = signof(net);
- } else {
- ntpdata->skew_delta = 0;
- }
+ /* Set the per-tick skew rate for the next second */
+ ntp_update_skew_delta(ntpdata, true);
return leap;
}
@@ -1003,6 +1022,10 @@ static inline void process_adjtimex_modes(struct ntp_data *ntpdata, const struct
if (txc->modes & (ADJ_TICK|ADJ_FREQUENCY|ADJ_OFFSET))
ntp_update_frequency(ntpdata);
+
+ /* A changed phase target invalidates the current skew rate */
+ if (txc->modes & (ADJ_OFFSET | ADJ_TIMECONST | ADJ_STATUS))
+ ntp_update_skew_delta(ntpdata, false);
}
/*
@@ -1023,6 +1046,7 @@ int ntp_adjtimex(unsigned int tkid, struct __kernel_timex *txc, const struct tim
ntpdata->time_adjust = txc->offset;
ntpdata->time_adjust_frac = 0;
ntp_update_frequency(ntpdata);
+ ntp_update_skew_delta(ntpdata, false);
audit_ntp_set_old(ad, AUDIT_NTP_ADJUST, save_adjust);
audit_ntp_set_new(ad, AUDIT_NTP_ADJUST, ntpdata->time_adjust);
@@ -1264,6 +1288,8 @@ static void hardpps_update_phase(struct ntp_data *ntpdata, long error)
/* Cancel running adjtime() */
ntpdata->time_adjust = 0;
ntpdata->time_adjust_frac = 0;
+ /* The previous correction's skew rate is stale; recompute */
+ ntp_update_skew_delta(ntpdata, false);
}
/* Update jitter */
ntpdata->pps_jitter += (jitter - ntpdata->pps_jitter) >> PPS_INTMIN;
--
2.43.0
next prev parent reply other threads:[~2026-10-01 20:21 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-01 20:21 [PATCH 0/5] timekeeping: Reduce magnitude of ntp_error David Woodhouse
2026-10-01 20:21 ` [PATCH 1/5] timekeeping: Allow tick_length changes to apply mid-tick David Woodhouse
2026-10-21 19:01 ` John Stultz
2026-10-22 13:39 ` David Woodhouse
2026-10-22 19:40 ` John Stultz
2026-10-01 20:21 ` David Woodhouse [this message]
2026-10-21 19:05 ` [PATCH 2/5] ntp: Recalculate skew_delta when the phase offset changes John Stultz
2026-10-01 20:21 ` [PATCH 3/5] timekeeping: Bound idle sleep while a phase slew is in flight David Woodhouse
2026-10-22 21:14 ` John Stultz
2026-10-22 21:49 ` David Woodhouse
2026-10-01 20:21 ` [PATCH 4/5] timekeeping: Reinstate proportional correction of ntp_error David Woodhouse
2026-10-05 10:04 ` Miroslav Lichvar
2026-10-05 13:17 ` David Woodhouse
2026-10-22 21:09 ` John Stultz
2026-10-01 20:21 ` [PATCH 5/5] [DO NOT MERGE] timekeeping: Apply extrapolated ntp_error to clock snapshots David Woodhouse
2026-10-02 8:30 ` Rodolfo Giometti
2026-10-02 9:14 ` David Woodhouse
2026-10-02 10:26 ` Rodolfo Giometti
2026-10-02 22:47 ` David Woodhouse
2026-10-05 8:15 ` Rodolfo Giometti
2026-10-05 8:49 ` David Woodhouse
2026-10-05 10:15 ` Rodolfo Giometti
2026-10-05 10:49 ` David Woodhouse
2026-10-21 14:37 ` [PATCH 0/5] timekeeping: Reduce magnitude of ntp_error David Woodhouse
2026-10-22 9:05 ` Rodolfo Giometti
2026-10-22 13:52 ` Miroslav Lichvar
2026-10-22 16:17 ` David Woodhouse
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=20261001202134.33929-3-dwmw2@infradead.org \
--to=dwmw2@infradead.org \
--cc=dwmw@amazon.co.uk \
--cc=giometti@enneenne.com \
--cc=jstultz@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=mlichvar@redhat.com \
--cc=ridouxj@amazon.com \
--cc=rluu@amazon.com \
--cc=sboyd@kernel.org \
--cc=tglx@kernel.org \
/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
all inboxes | Powered by JetHome®