From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from casper.infradead.org (casper.infradead.org [90.155.50.34]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 881C83DE439 for ; Wed, 20 May 2026 13:52:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=90.155.50.34 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779285149; cv=none; b=lvdAO1cjmvBw+T954Sb8s3xWuUEffNkdTfXsKYGTgbwic0z3q+e11XtjGSx3MZyqlkYhOP7KgJXr4lQxsdmp3BCDRLcmrpKh49n4tLWajdF0WERWRkMRsjjSMlfb8jqhe57BCauSC6YmSVqR+6Nx0YllovKwNKoanmM20cr42VI= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779285149; c=relaxed/simple; bh=01M4v7SHQfxs4U8XWCvW8MbkR1ugtScnAXcwU+JW0Ys=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bmV6GFAllPjXRrdocTgt3HQoKGfcIzTKdFQlpgbt6lA05gPV6c+1JFh874BHjKw8xXwnNtIVx5cofDbWH4Gmiw82lGLt7O8w2xB5/dNl+4bvob3G4LPDS2NyEl736pV4MfmiLSLQezktc4Biah9xI6YuGukw5JmTAQxw929PGFA= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org; spf=none smtp.mailfrom=casper.srs.infradead.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b=ljgClzro; arc=none smtp.client-ip=90.155.50.34 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=infradead.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=casper.srs.infradead.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=infradead.org header.i=@infradead.org header.b="ljgClzro" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; h=Sender:Content-Transfer-Encoding: MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc:To:From: Reply-To:Content-Type:Content-ID:Content-Description; bh=38iGrtb0lKcMY13D9sVP6SlOc0cqHJoLi18Pl08yD6M=; b=ljgClzro762kisie4L2//sgajX cM3wTtV0ebTfEiwk6Tv128Hz7jMlMk5tlJbP02YP7AgOcGYjAgcodElrJR8GCll37ze1EvN7b7E5s E3meAAfYn89nrzI68uBg1yjLcbN242dPjposzbZPLaVLxP0hCnlC8sygOiF6/QgY6JLpWXsR6d8Cb 51hak9NBotxlICHkA5CTaiJkidyrwGinExGtlYw26dWO8z+4MUV4wmhubCmYCrSkl8nbFHypFuthc 0yWJQtF+3fFlsA6rabhIEYsaDhDzrJZsRdR+EqQyl69LIYsZ6AsvyEiFGQhOAMtQ4eLU8Mkair+XT 8ncpAL3A==; Received: from i7.infradead.org ([2001:8b0:10b:1:21e:67ff:fecb:7a92]) by casper.infradead.org with esmtpsa (Exim 4.99.1 #2 (Red Hat Linux)) id 1wPhLG-00000007DRq-0esX; Wed, 20 May 2026 13:52:10 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.2 #2 (Red Hat Linux)) id 1wPhLF-000000009tc-3lko; Wed, 20 May 2026 14:52:09 +0100 From: David Woodhouse To: Richard Cochran , Wen Gu , David Woodhouse , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , John Stultz , Thomas Gleixner , Stephen Boyd , Anna-Maria Behnsen , Frederic Weisbecker , Shuah Khan , Peter Zijlstra , =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= , Arnd Bergmann , Miroslav Lichvar , Julien Ridoux , Ryan Luu , linux-kernel@vger.kernel.org Cc: David Woodhouse Subject: [RFC PATCH v3 08/10] timekeeping: Add absolute reference for feed-forward clock discipline Date: Wed, 20 May 2026 14:33:47 +0100 Message-ID: <20260520135207.37826-9-dwmw2@infradead.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260520135207.37826-1-dwmw2@infradead.org> References: <20260520135207.37826-1-dwmw2@infradead.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by casper.infradead.org. See http://www.infradead.org/rpr.html From: David Woodhouse Add timekeeping_set_reference() which allows an external clock source (such as a hypervisor vmclock) to provide an absolute time reference. The reference defines a linear counter-to-time mapping that the kernel uses to set both the frequency and phase of the system clock. When timekeeping_set_reference() is called: - tick_length is computed from the reference period and set via ntp_set_tick_length() - the phase delta is set via ntp_set_time_offset() So the NTP state is entirely consistent, and the existing time_offset skew mechanism then converges the clock to the reference, with the ntp_error and time_offset accumulators staying accurate throughout. Signed-off-by: David Woodhouse Assisted-by: Kiro:claude-opus-4.6-1m --- include/linux/timekeeping_reference.h | 19 ++++++++++++++ kernel/time/ntp.c | 14 ++++++++++ kernel/time/ntp_internal.h | 1 + kernel/time/timekeeping.c | 38 +++++++++++++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 include/linux/timekeeping_reference.h diff --git a/include/linux/timekeeping_reference.h b/include/linux/timekeeping_reference.h new file mode 100644 index 000000000000..4c1d8a6c02f1 --- /dev/null +++ b/include/linux/timekeeping_reference.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_TIMEKEEPING_REFERENCE_H +#define _LINUX_TIMEKEEPING_REFERENCE_H + +#include +#include + +struct tk_reference { + enum clocksource_ids cs_id; + u64 counter_value; + u64 time_sec; + u64 time_frac_sec; + u64 period_frac_sec; + u8 period_shift; +}; + +int timekeeping_set_reference(const struct tk_reference *ref); + +#endif diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c index 4494f258dd86..3dc098695665 100644 --- a/kernel/time/ntp.c +++ b/kernel/time/ntp.c @@ -403,6 +403,20 @@ void ntp_set_time_offset(unsigned int tkid, s64 offset_ns) ntpdata->time_adjust = 0; } +void ntp_set_tick_length(unsigned int tkid, u64 tick_length) +{ + struct ntp_data *ntpdata = &tk_ntp_data[tkid]; + u64 base; + + /* Compute the nominal second length (without frequency adjustment) */ + base = (u64)(ntpdata->tick_usec * NSEC_PER_USEC * USER_HZ) + << NTP_SCALE_SHIFT; + base += ntpdata->ntp_tick_adj; + + ntpdata->time_freq = (s64)(tick_length * NTP_INTERVAL_FREQ - base); + ntp_update_frequency(ntpdata); +} + /** * ntp_get_next_leap - Returns the next leapsecond in CLOCK_REALTIME ktime_t * @tkid: Timekeeper ID diff --git a/kernel/time/ntp_internal.h b/kernel/time/ntp_internal.h index 639860ff2baf..14ca8bc08120 100644 --- a/kernel/time/ntp_internal.h +++ b/kernel/time/ntp_internal.h @@ -9,6 +9,7 @@ extern u64 ntp_tick_length(unsigned int tkid); extern s64 ntp_get_skew_delta(unsigned int tkid); extern s64 ntp_drain_time_offset(unsigned int tkid, s64 amount); extern void ntp_set_time_offset(unsigned int tkid, s64 offset_ns); +extern void ntp_set_tick_length(unsigned int tkid, u64 tick_length); extern ktime_t ntp_get_next_leap(unsigned int tkid); extern int second_overflow(unsigned int tkid, time64_t secs); extern int ntp_adjtimex(unsigned int tkid, struct __kernel_timex *txc, const struct timespec64 *ts, diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 27b2a093b138..5c4b377505bc 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -2324,6 +2324,44 @@ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk, * Adjust the timekeeper's multiplier to the correct frequency * and also to reduce the accumulated error value. */ + +#include + + +int timekeeping_set_reference(const struct tk_reference *ref) +{ + struct timekeeper *tk = &tk_core.timekeeper; + u64 new_tl, delta, ref_frac; + s64 ref_err; + unsigned long flags; + + raw_spin_lock_irqsave(&tk_core.lock, flags); + + if (tk->cs_id != ref->cs_id) { + raw_spin_unlock_irqrestore(&tk_core.lock, flags); + return -ENODEV; + } + + new_tl = mul_u64_u64_shr(ref->period_frac_sec, + (u64)tk->cycle_interval * NSEC_PER_SEC, + 32 + ref->period_shift); + ntp_set_tick_length(tk->id, new_tl); + + /* Compute phase offset at cycle_last and set time_offset to slew */ + delta = tk->tkr_mono.cycle_last - ref->counter_value; + ref_frac = mul_u64_u64_shr(delta, ref->period_frac_sec, + ref->period_shift) + ref->time_frac_sec; + ref_err = (s64)mul_u64_u64_shr(ref_frac, + (u64)NSEC_PER_SEC << tk->tkr_mono.shift, 64) - + (s64)tk->tkr_mono.xtime_nsec; + ntp_set_time_offset(tk->id, ref_err >> tk->tkr_mono.shift); + tk->ntp_error = 0; + + raw_spin_unlock_irqrestore(&tk_core.lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(timekeeping_set_reference); + static void timekeeping_adjust(struct timekeeper *tk, s64 offset) { u64 ntp_tl = ntp_tick_length(tk->id); -- 2.54.0