mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: David Woodhouse <dwmw2@infradead.org>
To: "Richard Cochran" <richardcochran@gmail.com>,
	"Wen Gu" <guwen@linux.alibaba.com>,
	"David Woodhouse" <dwmw2@infradead.org>,
	"Andrew Lunn" <andrew+netdev@lunn.ch>,
	"David S. Miller" <davem@davemloft.net>,
	"Eric Dumazet" <edumazet@google.com>,
	"Jakub Kicinski" <kuba@kernel.org>,
	"Paolo Abeni" <pabeni@redhat.com>,
	"John Stultz" <jstultz@google.com>,
	"Thomas Gleixner" <tglx@kernel.org>,
	"Stephen Boyd" <sboyd@kernel.org>,
	"Anna-Maria Behnsen" <anna-maria@linutronix.de>,
	"Frederic Weisbecker" <frederic@kernel.org>,
	"Shuah Khan" <shuah@kernel.org>,
	"Peter Zijlstra" <peterz@infradead.org>,
	"Thomas Weißschuh" <thomas.weissschuh@linutronix.de>,
	"Arnd Bergmann" <arnd@arndb.de>,
	"Miroslav Lichvar" <mlichvar@redhat.com>,
	"Julien Ridoux" <ridouxj@amazon.com>,
	"Ryan Luu" <rluu@amazon.com>,
	linux-kernel@vger.kernel.org
Subject: [PATCH v7 6/7] timekeeping: Settle competing time_offset and time_adjust skew
Date: Sun, 21 Jun 2026 22:53:59 +0100	[thread overview]
Message-ID: <20260621220051.1030462-7-dwmw2@infradead.org> (raw)
In-Reply-To: <20260621220051.1030462-1-dwmw2@infradead.org>

From: David Woodhouse <dwmw@amazon.co.uk>

time_offset (the exponential PLL phase slew) and time_adjust (the
linear adjtime() slew) can be asked to move the clock in opposite
directions. second_overflow() folds only their *net* into the per-tick
skew_delta, so the cancelling overlap would never be drained from
either tracker by the per-tick code — and if they cancel exactly,
skew_delta is zero and neither converges at all.

Arguably we could just let one of them entirely cancel out the other
immediately, but that would be a change in userspace-visible behaviour.

Instead, preserve the existing behaviour by calculating the "conflict"
portion between the opposing skew each second, and transferring that
amount directly from one tracker to the other.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: Kiro:claude-opus-4.8
---
 kernel/time/ntp.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 75 insertions(+)

diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index 20d211b27908..73a47dd775a0 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -514,6 +514,69 @@ s64 ntp_drain_skew(unsigned int tkid, s64 amount, unsigned int shift)
 	return amount - unclaimed;
 }
 
+/*
+ * time_offset (drained exponentially) and time_adjust (drained linearly at the
+ * MAX_TICKADJ rate) can be asked to slew the clock in opposite directions.
+ * second_overflow() only folds their *net* into skew_delta, so the cancelling
+ * part would never be drained from either tracker via the per-tick code -- and
+ * if they cancel exactly, skew_delta is zero and neither converges at all.
+ *
+ * Settle that cancelling phase directly between the two here. No clock motion
+ * results (the opposing slews annihilate), but both move toward zero so neither
+ * stalls. @amount is the phase to take off time_offset, in its (÷HZ) units and
+ * with its sign; the same real magnitude comes off time_adjust in the opposite
+ * direction. Clamped so neither tracker is driven past zero.
+ */
+static void ntp_transfer_offset_adjust(struct ntp_data *ntpdata, s64 amount)
+{
+	s64 frac_delta, carry;
+
+	/*
+	 * Don't drain time_offset past zero. @amount shares its sign and is
+	 * normally bounded below it by ntp_offset_chunk(), but the ±1 skew_delta
+	 * floor for a tiny time_offset can exceed it, so clamp.
+	 */
+	if (abs(amount) > abs(ntpdata->time_offset))
+		amount = ntpdata->time_offset;
+	if (!amount)
+		return;
+
+	/*
+	 * Remove the matching phase from time_adjust, in plain shifted-ns. No
+	 * clamp against time_adjust's zero is needed: @amount is bounded by the
+	 * adjtime chunk, which second_overflow() never lets exceed time_adjust's
+	 * own pending phase, so this cannot overshoot.
+	 */
+	frac_delta = amount * NTP_INTERVAL_FREQ;
+
+	ntpdata->time_offset -= amount;
+
+	/* Add the matching phase to time_adjust, carrying whole µs (O(1)). */
+	ntpdata->time_adjust_frac += frac_delta;
+	if (ntpdata->time_adjust_frac >= ONE_US_NS ||
+	    ntpdata->time_adjust_frac <= -ONE_US_NS) {
+		carry = div64_s64(ntpdata->time_adjust_frac, ONE_US_NS);
+		ntpdata->time_adjust	  += carry;
+		ntpdata->time_adjust_frac -= carry * ONE_US_NS;
+	}
+
+	/*
+	 * Keep time_adjust and its sub-µs remainder the same sign. The
+	 * truncating carry above can leave them opposed (e.g. +4 µs paired
+	 * with -250 ns), and ntp_drain_time_adjust() treats abs(time_adjust_frac)
+	 * as same-direction drawer capacity -- an opposing remainder there makes
+	 * it over-deliver phase that was never removed from the pile. Borrow or
+	 * repay a single whole µs to realign; the total phase is unchanged.
+	 */
+	if (ntpdata->time_adjust > 0 && ntpdata->time_adjust_frac < 0) {
+		ntpdata->time_adjust--;
+		ntpdata->time_adjust_frac += ONE_US_NS;
+	} else if (ntpdata->time_adjust < 0 && ntpdata->time_adjust_frac > 0) {
+		ntpdata->time_adjust++;
+		ntpdata->time_adjust_frac -= ONE_US_NS;
+	}
+}
+
 /**
  * ntp_get_next_leap - Returns the next leapsecond in CLOCK_REALTIME ktime_t
  * @tkid:	Timekeeper ID
@@ -651,6 +714,18 @@ int second_overflow(unsigned int tkid, time64_t secs)
 				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);
-- 
2.54.0


  parent reply	other threads:[~2026-06-21 22:01 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-21 21:53 [PATCH v7 0/7] timekeeping/ntp: Fix drift tracking precision David Woodhouse
2026-06-21 21:53 ` [PATCH v7 1/7] MAINTAINERS: Add Miroslav as timekeeping reviewer David Woodhouse
2026-07-07 22:22   ` [tip: timers/core] " tip-bot2 for David Woodhouse
2026-06-21 21:53 ` [PATCH v7 2/7] timekeeping: Account for monotonicity adjustment in ntp_error David Woodhouse
2026-07-07 22:22   ` [tip: timers/core] " tip-bot2 for David Woodhouse
2026-06-21 21:53 ` [PATCH v7 3/7] timekeeping: Account for clocksource tick quantisation via NTP David Woodhouse
2026-06-22 17:52   ` John Stultz
2026-07-07 19:27     ` David Woodhouse
2026-07-07 22:22   ` [tip: timers/core] " tip-bot2 for David Woodhouse
     [not found]   ` <CGME20260709125630eucas1p2d1acb4893a1c72dfba5312453c0971f2@eucas1p2.samsung.com>
2026-07-09 12:56     ` [PATCH v7 3/7] " Marek Szyprowski
2026-07-09 13:19       ` Arnd Bergmann
2026-07-09 14:10         ` Marek Szyprowski
2026-07-09 13:38       ` David Woodhouse
2026-07-09 14:42         ` Marek Szyprowski
2026-07-09 16:08       ` David Woodhouse
2026-07-09 16:21         ` Marek Szyprowski
2026-07-09 16:24           ` David Woodhouse
2026-07-10  7:25   ` [tip: timers/core] " tip-bot2 for David Woodhouse
2026-06-21 21:53 ` [PATCH v7 4/7] timekeeping: Drive time_offset skew via per-tick ntp_error transfer David Woodhouse
2026-07-07 22:22   ` [tip: timers/core] " tip-bot2 for David Woodhouse
2026-07-10  7:25   ` tip-bot2 for David Woodhouse
2026-06-21 21:53 ` [PATCH v7 5/7] timekeeping: Drive time_adjust " David Woodhouse
2026-07-07 22:22   ` [tip: timers/core] " tip-bot2 for David Woodhouse
2026-07-10  7:25   ` tip-bot2 for David Woodhouse
2026-06-21 21:53 ` David Woodhouse [this message]
2026-07-07 22:22   ` [tip: timers/core] timekeeping: Settle competing time_offset and time_adjust skew tip-bot2 for David Woodhouse
2026-07-10  7:25   ` tip-bot2 for David Woodhouse
2026-06-21 21:54 ` [PATCH v7 7/7] ntp: Remove tick_length_base, use tick_length directly David Woodhouse
2026-07-07 22:22   ` [tip: timers/core] " tip-bot2 for David Woodhouse
2026-07-10  7:25   ` tip-bot2 for 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=20260621220051.1030462-7-dwmw2@infradead.org \
    --to=dwmw2@infradead.org \
    --cc=andrew+netdev@lunn.ch \
    --cc=anna-maria@linutronix.de \
    --cc=arnd@arndb.de \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=frederic@kernel.org \
    --cc=guwen@linux.alibaba.com \
    --cc=jstultz@google.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mlichvar@redhat.com \
    --cc=pabeni@redhat.com \
    --cc=peterz@infradead.org \
    --cc=richardcochran@gmail.com \
    --cc=ridouxj@amazon.com \
    --cc=rluu@amazon.com \
    --cc=sboyd@kernel.org \
    --cc=shuah@kernel.org \
    --cc=tglx@kernel.org \
    --cc=thomas.weissschuh@linutronix.de \
    /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