mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock
@ 2026-05-20 13:33 David Woodhouse
  2026-05-20 13:33 ` [RFC PATCH v3 01/10] MAINTAINERS: Add Miroslav as timekeeping reviewer David Woodhouse
                   ` (10 more replies)
  0 siblings, 11 replies; 21+ messages in thread
From: David Woodhouse @ 2026-05-20 13:33 UTC (permalink / raw)
  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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel

This is v3 of the series to allow feed-forward clock discipline, allowing
a guest kernel to lock its system clock directly to a hypervisor-provided
vmclock reference with nanosecond precision and no drift.

With all the drift-inducing bugs in the core timekeeping resolved in the 
first patches of the series, the RFC timekeeping_set_reference() 
function basically just sets the tick length and time_offset according 
to the reference, and lets the now-fixed core timekeeping get on with 
its job.

The vmclock device (https://uapi-group.org/specifications/specs/vmclock/)
provides a shared memory page containing a linear time function:
time = base + (counter - counter_value) × period. The guest can read
this at any time to determine the hypervisor's view of the current time,
without a VM exit. Unlike guest-driven NTP, it allows for accurate time
to be preserved across live migration.

The existing ptp_vmclock driver already exposes this as a PTP clock for
userspace consumers (phc2sys, chrony). This series adds kernel-internal
consumption: the tick mechanism can clamp directly to the vmclock
reference, eliminating the need for NTP to discipline the guest clock.

Now testing *without* nohz in the guest, I no longer see single-digit
nanoseconds of jitter; I wouldn't describe it as being that high. It
goes to ±1ns and stays there, although it does take a while to converge
since dropping the exponential tail clamping.

Changes since v2:
 • Renamed "clawback" to "monotonicity adjustment" throughout (patch 2).
 • Drop the exponential tail clamping (v2 patch 3).
 • Convert adjtime() to use time_offset to deliver skew too, and remove
   the separate 'tick_length_base' as adjusting tick_length directly is
   no longer used to skew the clock. The skew_delta basically does the
   same thing, but is easier to get the accounting right.
 • The timekeeping_set_reference() API (patch 8) now takes tk_core.lock
   and computes the phase offset internally, eliminating the race window
   that existed in v2 between setting the reference and the tick code
   consuming it.
 • vmclock_host (patch 10) is no longer marked WIP — it has a selftest
   and proper locking (but is still RFC).
 • Added MAINTAINERS entry for Miroslav Lichvar as timekeeping reviewer
   (patch 1).

The series:

Patch 1: MAINTAINERS update
  1. Add Miroslav Lichvar as timekeeping reviewer.

Patches 2-3: Timekeeping bugfixes (suitable for stable/independent review)
  2. Remove stale xtime_remainder from ntp_error accumulation.
  3. Account for monotonicity adjustment in ntp_error.

Patch 4: Independent bugfix
  4. Guard against divide-by-zero during clocksource recalibration.

Patches 5-7: NTP rework — eliminate tick_length_base
  5. Drive time_offset skew via per-tick ntp_error transfer instead of
     tick_length inflation, with mult adjustment for dithering bandwidth.
  6. Convert adjtime() to use time_offset directly instead of inflating
     tick_length, removing the rounding loss that prevented convergence.
  7. Remove tick_length_base entirely — tick_length is now always the
     NTP-disciplined value with no per-tick inflation.

Patches 8-9: Feed-forward reference clock infrastructure
  8. Add timekeeping_set_reference() API for external clock references.
  9. Wire ptp_vmclock to call timekeeping_set_reference() on probe.

Patch 10: Host-side vmclock page export
  10. Add /dev/vmclock_host miscdev for VMM consumption, with selftest.

Tested with QEMU passing through a vmclock device to a guest. The guest
clock converges to the reference within seconds and remains within
single-digit nanoseconds indefinitely, with no further external
correction.

https://git.infradead.org/?p=users/dwmw2/qemu.git;a=shortlog;h=refs/heads/vmclock-passthrough


David Woodhouse (10):
      MAINTAINERS: Add Miroslav as timekeeping reviewer
      timekeeping: Remove xtime_remainder from ntp_error accumulation
      timekeeping: Account for monotonicity adjustment in ntp_error
      timekeeping: Guard against divide-by-zero in timekeeping_adjust
      timekeeping: Drive time_offset skew via per-tick ntp_error transfer
      ntp: Convert adjtime() to use time_offset instead of tick_length inflation
      ntp: Remove tick_length_base, use tick_length directly
      timekeeping: Add absolute reference for feed-forward clock discipline
      ptp_vmclock: Feed reference to timekeeping for feed-forward discipline
      kernel/time: Add /dev/vmclock_host miscdev

 MAINTAINERS                                        |   1 +
 drivers/ptp/ptp_vmclock.c                          |  95 +++++
 include/linux/timekeeper_internal.h                |   6 +-
 include/linux/timekeeping_reference.h              |  19 +
 kernel/time/Kconfig                                |   7 +
 kernel/time/Makefile                               |   1 +
 kernel/time/ntp.c                                  | 132 +++++--
 kernel/time/ntp_internal.h                         |   9 +
 kernel/time/timekeeping.c                          |  76 +++-
 kernel/time/vmclock_host.c                         | 391 +++++++++++++++++++++
 tools/testing/selftests/timers/vmclock_host_test.c | 171 +++++++++
 11 files changed, 867 insertions(+), 41 deletions(-)


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [RFC PATCH v3 01/10] MAINTAINERS: Add Miroslav as timekeeping reviewer
  2026-05-20 13:33 [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
@ 2026-05-20 13:33 ` David Woodhouse
  2026-05-20 23:28   ` John Stultz
  2026-05-20 13:33 ` [RFC PATCH v3 02/10] timekeeping: Remove xtime_remainder from ntp_error accumulation David Woodhouse
                   ` (9 subsequent siblings)
  10 siblings, 1 reply; 21+ messages in thread
From: David Woodhouse @ 2026-05-20 13:33 UTC (permalink / raw)
  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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel
  Cc: David Woodhouse

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

If Thomas is going to nudge me on IRC to add Miroslav to Cc on
timekeeping patches, then he might as well actually be listed in the
MAINTAINERS file.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 MAINTAINERS | 1 +
 1 file changed, 1 insertion(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 7b49c5fa7054..fefc2b007b87 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26892,6 +26892,7 @@ TIMEKEEPING, CLOCKSOURCE CORE, NTP, ALARMTIMER
 M:	John Stultz <jstultz@google.com>
 M:	Thomas Gleixner <tglx@kernel.org>
 R:	Stephen Boyd <sboyd@kernel.org>
+R:	Miroslav Lichvar <mlichvar@redhat.com>
 L:	linux-kernel@vger.kernel.org
 S:	Supported
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers/core
-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [RFC PATCH v3 02/10] timekeeping: Remove xtime_remainder from ntp_error accumulation
  2026-05-20 13:33 [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
  2026-05-20 13:33 ` [RFC PATCH v3 01/10] MAINTAINERS: Add Miroslav as timekeeping reviewer David Woodhouse
@ 2026-05-20 13:33 ` David Woodhouse
  2026-05-22 11:41   ` David Woodhouse
  2026-05-20 13:33 ` [RFC PATCH v3 03/10] timekeeping: Account for monotonicity adjustment in ntp_error David Woodhouse
                   ` (8 subsequent siblings)
  10 siblings, 1 reply; 21+ messages in thread
From: David Woodhouse @ 2026-05-20 13:33 UTC (permalink / raw)
  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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel
  Cc: David Woodhouse

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

The ntp_error accumulator tracks the difference between the time actually
reported to consumers in xtime, and the *intended* time. The former is
subject to a sawtooth effect due to the quantisation of 'mult', which
means that it actually advances by 'xtime_interval' each tick, while
the intended clock advances by 'ntp_tick'.

By dithering between adjacent integer values of 'mult' which result in
an 'xtime_interval' slightly higher/lower than the intended tick length,
the advancement of xtime is kept on average to the intended rate.

The accounting should therefore adjust ntp_error by adding ntp_tick and
subtracting xtime_interval on each tick.

Since commit a386b5af8edd ("time: Compensate for rounding on
odd-frequency clocksources") the value subtracted has been
(xtime_interval + xtime_remainder), which is wrong. The effect is a
systematic drift whose magnitude depends on the value of xtime_remainder
and the NTP frequency correction. NTP masks this by continuously
adjusting the frequency to compensate, but with a fixed frequency (or an
external reference clock like vmclock), the drift is exposed.

The value of xtime_remainder actually does represent the difference
between the tick period and xtime_interval, so simply adding it instead
of (+ tick length - xtime_remainder) might have made sense... except
that it's only calculated once at boot time, so it's inaccurate anyway.
So just kill it with fire.

Also remove it from the mult computation in timekeeping_adjust(), which
used it to offset the division for the same (incorrect) reason.

Fixes: a386b5af8edd ("time: Compensate for rounding on odd-frequency clocksources")
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: Kiro:claude-opus-4.6-1m
---
 include/linux/timekeeper_internal.h | 3 ---
 kernel/time/timekeeping.c           | 8 +++-----
 2 files changed, 3 insertions(+), 8 deletions(-)

diff --git a/include/linux/timekeeper_internal.h b/include/linux/timekeeper_internal.h
index e36d11e33e0c..da6cf383bedc 100644
--- a/include/linux/timekeeper_internal.h
+++ b/include/linux/timekeeper_internal.h
@@ -84,8 +84,6 @@ struct tk_read_base {
  * @cycle_interval:		Number of clock cycles in one NTP interval
  * @xtime_interval:		Number of clock shifted nano seconds in one NTP
  *				interval.
- * @xtime_remainder:		Shifted nano seconds left over when rounding
- *				@cycle_interval
  * @raw_interval:		Shifted raw nano seconds accumulated per NTP interval.
  * @next_leap_ktime:		CLOCK_MONOTONIC time value of a pending leap-second
  * @ntp_tick:			The ntp_tick_length() value currently being
@@ -178,7 +176,6 @@ struct timekeeper {
 
 	u64			cycle_interval;
 	u64			xtime_interval;
-	s64			xtime_remainder;
 	u64			raw_interval;
 
 	ktime_t			next_leap_ktime;
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index c493a4010305..b84b05f9d460 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -360,7 +360,6 @@ static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock)
 
 	/* Go back from cycles -> shifted ns */
 	tk->xtime_interval = interval * clock->mult;
-	tk->xtime_remainder = ntpinterval - tk->xtime_interval;
 	tk->raw_interval = interval * clock->mult;
 
 	 /* if changing clocks, convert xtime_nsec shift units */
@@ -2337,8 +2336,8 @@ static void timekeeping_adjust(struct timekeeper *tk, s64 offset)
 		mult = tk->tkr_mono.mult - tk->ntp_err_mult;
 	} else {
 		tk->ntp_tick = ntp_tl;
-		mult = div64_u64((tk->ntp_tick >> tk->ntp_error_shift) -
-				 tk->xtime_remainder, tk->cycle_interval);
+		mult = div64_u64(tk->ntp_tick >> tk->ntp_error_shift,
+				 tk->cycle_interval);
 	}
 
 	/*
@@ -2463,8 +2462,7 @@ static u64 logarithmic_accumulation(struct timekeeper *tk, u64 offset,
 
 	/* Accumulate error between NTP and clock interval */
 	tk->ntp_error += tk->ntp_tick << shift;
-	tk->ntp_error -= (tk->xtime_interval + tk->xtime_remainder) <<
-						(tk->ntp_error_shift + shift);
+	tk->ntp_error -= tk->xtime_interval << (tk->ntp_error_shift + shift);
 
 	return offset;
 }
-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [RFC PATCH v3 03/10] timekeeping: Account for monotonicity adjustment in ntp_error
  2026-05-20 13:33 [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
  2026-05-20 13:33 ` [RFC PATCH v3 01/10] MAINTAINERS: Add Miroslav as timekeeping reviewer David Woodhouse
  2026-05-20 13:33 ` [RFC PATCH v3 02/10] timekeeping: Remove xtime_remainder from ntp_error accumulation David Woodhouse
@ 2026-05-20 13:33 ` David Woodhouse
  2026-05-20 23:27   ` John Stultz
  2026-05-20 13:33 ` [RFC PATCH v3 04/10] timekeeping: Guard against divide-by-zero in timekeeping_adjust David Woodhouse
                   ` (7 subsequent siblings)
  10 siblings, 1 reply; 21+ messages in thread
From: David Woodhouse @ 2026-05-20 13:33 UTC (permalink / raw)
  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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel
  Cc: David Woodhouse

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

timekeeping_apply_adjustment() modifies xtime_nsec to maintain vDSO
monotonicity when mult changes:

    xtime_nsec -= offset

This ensures that the time reported to userspace does not jump when the
multiplier is adjusted from one tick to the next. However, the ntp_error
accumulator which tracks the difference between intended and actual
clock position was not being updated updated to reflect this additional
discrepancy.

An earlier attempt at this compensation existed as:

    ntp_error -= (interval - offset) << ntp_error_shift

but was removed in commit c2cda2a5bda9 ("timekeeping/ntp: Don't align
NTP frequency adjustments to ticks") because it was a major source of
NTP error. That's because (interval - offset) was wrong: the subtraction
of "interval" prematurely accounted for the changed xtime_interval of
the next tick, which would be correctly accounted in the next
accumulation anyway — a double subtraction.

What is actually needed is just the "offset" part: ntp_error must be
told that xtime_nsec moved by "offset" without a corresponding change
in the intended position. For the normal ±1 mult dithering this is
negligible (the adjustments cancel over time), but for larger mult
changes — such as when an external reference clock sets a new
frequency — the one-time uncompensated offset is significant.

Fix by adjusting ntp_error by the correct amount:

    ntp_error += offset << ntp_error_shift

This keeps ntp_error consistent with the actual xtime_nsec position
after the adjustment.

Fixes: c2cda2a5bda9 ("timekeeping/ntp: Don't align NTP frequency adjustments to ticks")
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: Kiro:claude-opus-4.6-1m
---
 kernel/time/timekeeping.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index b84b05f9d460..95973e45d456 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -2317,6 +2317,7 @@ 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;
 }
 
 /*
-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [RFC PATCH v3 04/10] timekeeping: Guard against divide-by-zero in timekeeping_adjust
  2026-05-20 13:33 [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
                   ` (2 preceding siblings ...)
  2026-05-20 13:33 ` [RFC PATCH v3 03/10] timekeeping: Account for monotonicity adjustment in ntp_error David Woodhouse
@ 2026-05-20 13:33 ` David Woodhouse
  2026-05-20 23:29   ` John Stultz
  2026-05-20 13:33 ` [RFC PATCH v3 05/10] timekeeping: Drive time_offset skew via per-tick ntp_error transfer David Woodhouse
                   ` (6 subsequent siblings)
  10 siblings, 1 reply; 21+ messages in thread
From: David Woodhouse @ 2026-05-20 13:33 UTC (permalink / raw)
  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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel
  Cc: David Woodhouse

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

When the TSC clocksource is recalibrated (e.g. on KVM guests with
clocksource=tsc), cycle_interval can momentarily be zero during the
transition. Guard the div64_u64 in timekeeping_adjust() to prevent a
divide-by-zero oops.

This can be triggered on KVM guests that force clocksource=tsc when
the guest's measured TSC frequency doesn't match what KVM reported,
causing a recalibration during boot.

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

diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index 95973e45d456..0d5faa5d13d7 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -2336,6 +2336,8 @@ static void timekeeping_adjust(struct timekeeper *tk, s64 offset)
 	if (likely(tk->ntp_tick == ntp_tl)) {
 		mult = tk->tkr_mono.mult - tk->ntp_err_mult;
 	} else {
+		if (unlikely(!tk->cycle_interval))
+			return;
 		tk->ntp_tick = ntp_tl;
 		mult = div64_u64(tk->ntp_tick >> tk->ntp_error_shift,
 				 tk->cycle_interval);
-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [RFC PATCH v3 05/10] timekeeping: Drive time_offset skew via per-tick ntp_error transfer
  2026-05-20 13:33 [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
                   ` (3 preceding siblings ...)
  2026-05-20 13:33 ` [RFC PATCH v3 04/10] timekeeping: Guard against divide-by-zero in timekeeping_adjust David Woodhouse
@ 2026-05-20 13:33 ` David Woodhouse
  2026-05-20 13:33 ` [RFC PATCH v3 06/10] ntp: Convert adjtime() to use time_offset instead of tick_length inflation David Woodhouse
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 21+ messages in thread
From: David Woodhouse @ 2026-05-20 13:33 UTC (permalink / raw)
  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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel
  Cc: David Woodhouse

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

Instead of inflating tick_length to effect the time_offset slew,
transfer the skew to ntp_error per-tick and drain time_offset at the
equivalent per-tick rate:

 - ntp_error += skew_delta << shift (biases dithering to deliver skew)
 - time_offset -= skew_delta / NTP_INTERVAL_FREQ (per-tick drain)

Compute mult from (ntp_tick + skew_delta) so the dithering has enough
bandwidth to deliver the skew rate by selecting between mult and mult+1.
This is equivalent to the old tick_length += delta approach but without
modifying tick_length, and with exact per-tick accounting of the
time_offset drain.

To eliminate remainder error in the per-tick division, skew_delta is
rounded to a multiple of NTP_INTERVAL_FREQ in second_overflow().

second_overflow() computes skew_delta (the exponential decay rate)
but no longer drains time_offset or inflates tick_length directly.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: Kiro:claude-opus-4.6-1m
---
 include/linux/timekeeper_internal.h |  1 +
 kernel/time/ntp.c                   | 35 +++++++++++++++++++++++++++--
 kernel/time/ntp_internal.h          |  2 ++
 kernel/time/timekeeping.c           | 29 +++++++++++++++++++-----
 4 files changed, 60 insertions(+), 7 deletions(-)

diff --git a/include/linux/timekeeper_internal.h b/include/linux/timekeeper_internal.h
index da6cf383bedc..9de6b5b94dc0 100644
--- a/include/linux/timekeeper_internal.h
+++ b/include/linux/timekeeper_internal.h
@@ -184,6 +184,7 @@ struct timekeeper {
 	u32			ntp_error_shift;
 	u32			ntp_err_mult;
 	u32			skip_second_overflow;
+	s64			skew_delta;
 	s32			tai_offset;
 };
 
diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index 97fa99b96dd0..87f3f5d0d13d 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -63,6 +63,7 @@ struct ntp_data {
 	int			time_state;
 	int			time_status;
 	s64			time_offset;
+	s64			skew_delta;
 	long			time_constant;
 	long			time_maxerror;
 	long			time_esterror;
@@ -364,6 +365,31 @@ u64 ntp_tick_length(unsigned int tkid)
 	return tk_ntp_data[tkid].tick_length;
 }
 
+s64 ntp_get_skew_delta(unsigned int tkid)
+{
+	return tk_ntp_data[tkid].skew_delta;
+}
+
+s64 ntp_drain_time_offset(unsigned int tkid, s64 amount)
+{
+	struct ntp_data *ntpdata = &tk_ntp_data[tkid];
+
+	/* Only drain if amount and time_offset have the same sign */
+	if (!amount || (amount > 0) != (ntpdata->time_offset > 0))
+		return amount;
+
+	/* Clamp: don't overshoot zero */
+	if (abs(amount) > abs(ntpdata->time_offset)) {
+		s64 undrained = amount - ntpdata->time_offset;
+
+		ntpdata->time_offset = 0;
+		return undrained;
+	}
+
+	ntpdata->time_offset -= amount;
+	return 0;
+}
+
 /**
  * ntp_get_next_leap - Returns the next leapsecond in CLOCK_REALTIME ktime_t
  * @tkid:	Timekeeper ID
@@ -460,9 +486,14 @@ int second_overflow(unsigned int tkid, time64_t secs)
 	/* Compute the phase adjustment for the next second */
 	ntpdata->tick_length	 = ntpdata->tick_length_base;
 
+	/*
+	 * Set the per-tick skew rate for the tick code. This is in the
+	 * same units as tick_length (ns << NTP_SCALE_SHIFT), and is
+	 * rounded to a multiple of NTP_INTERVAL_FREQ so that the per-tick
+	 * division in the tick code is exact.
+	 */
 	delta			 = ntp_offset_chunk(ntpdata, ntpdata->time_offset);
-	ntpdata->time_offset	-= delta;
-	ntpdata->tick_length	+= delta;
+	ntpdata->skew_delta	 = delta - delta % NTP_INTERVAL_FREQ;
 
 	/* Check PPS signal */
 	pps_dec_valid(ntpdata);
diff --git a/kernel/time/ntp_internal.h b/kernel/time/ntp_internal.h
index 7084d839c207..05e5dd5e1b70 100644
--- a/kernel/time/ntp_internal.h
+++ b/kernel/time/ntp_internal.h
@@ -6,6 +6,8 @@ extern void ntp_init(void);
 extern void ntp_clear(unsigned int tkid);
 /* Returns how long ticks are at present, in ns / 2^NTP_SCALE_SHIFT. */
 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 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 0d5faa5d13d7..27b2a093b138 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -2327,20 +2327,23 @@ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk,
 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);
 	u32 mult;
 
 	/*
-	 * Determine the multiplier from the current NTP tick length.
-	 * Avoid expensive division when the tick length doesn't change.
+	 * Determine the multiplier from the current NTP tick length plus
+	 * skew_delta. The skew biases mult so that ±1 dithering can deliver
+	 * the time_offset slew rate. Recompute when either changes.
 	 */
-	if (likely(tk->ntp_tick == ntp_tl)) {
+	if (likely(tk->ntp_tick == ntp_tl && tk->skew_delta == skew)) {
 		mult = tk->tkr_mono.mult - tk->ntp_err_mult;
 	} else {
 		if (unlikely(!tk->cycle_interval))
 			return;
 		tk->ntp_tick = ntp_tl;
-		mult = div64_u64(tk->ntp_tick >> tk->ntp_error_shift,
-				 tk->cycle_interval);
+		tk->skew_delta = skew;
+		mult = div64_u64((tk->ntp_tick + skew) >> tk->ntp_error_shift,
+				  tk->cycle_interval);
 	}
 
 	/*
@@ -2467,6 +2470,22 @@ static u64 logarithmic_accumulation(struct timekeeper *tk, u64 offset,
 	tk->ntp_error += tk->ntp_tick << shift;
 	tk->ntp_error -= tk->xtime_interval << (tk->ntp_error_shift + shift);
 
+	/*
+	 * During clock skew driven by ntpdata->time_offset, transfer a
+	 * *portion* of the requested total delta into ntp_error from
+	 * time_offset each tick. The second_overflow() function sets
+	 * the rate of skew, and the value of 'mult' has been selected
+	 * in order to allow the dithering to keep ntp_error around zero
+	 * even while this adjustment is being applied.
+	 */
+	if (tk->skew_delta) {
+		s64 drain = div_s64(tk->skew_delta << shift,
+				    NTP_INTERVAL_FREQ);
+
+		tk->ntp_error += (tk->skew_delta << shift) -
+				 ntp_drain_time_offset(tk->id, drain);
+	}
+
 	return offset;
 }
 
-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [RFC PATCH v3 06/10] ntp: Convert adjtime() to use time_offset instead of tick_length inflation
  2026-05-20 13:33 [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
                   ` (4 preceding siblings ...)
  2026-05-20 13:33 ` [RFC PATCH v3 05/10] timekeeping: Drive time_offset skew via per-tick ntp_error transfer David Woodhouse
@ 2026-05-20 13:33 ` David Woodhouse
  2026-05-20 13:33 ` [RFC PATCH v3 07/10] ntp: Remove tick_length_base, use tick_length directly David Woodhouse
                   ` (4 subsequent siblings)
  10 siblings, 0 replies; 21+ messages in thread
From: David Woodhouse @ 2026-05-20 13:33 UTC (permalink / raw)
  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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel
  Cc: David Woodhouse

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

The legacy adjtime() syscall (ADJ_ADJTIME/ADJ_OFFSET_SINGLESHOT) used
to slew the clock by inflating tick_length directly via time_adjust.
This was the last remaining user of tick_length != tick_length_base.

Convert it to fold time_adjust into time_offset each second (up to
MAX_TICKADJ per second, same rate limit as before). The existing
time_offset skew mechanism then delivers it via the per-tick ntp_error
transfer and mult adjustment.

Introduce ntp_set_time_offset() helper for setting time_offset from a
nanosecond value, and refactor ntp_update_offset() to use it. This
helper will also be used by the feed-forward reference clock API in a
subsequent commit.

This eliminates the last source of tick_length inflation, making
tick_length always equal tick_length_base.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: Kiro:claude-opus-4.6-1m
---
 kernel/time/ntp.c          | 52 ++++++++++++++++++++++++--------------
 kernel/time/ntp_internal.h |  1 +
 2 files changed, 34 insertions(+), 19 deletions(-)

diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index 87f3f5d0d13d..2b75653b456c 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -329,7 +329,7 @@ static void ntp_update_offset(struct ntp_data *ntpdata, long offset)
 
 	ntpdata->time_freq   = max(freq_adj, -MAXFREQ_SCALED);
 
-	ntpdata->time_offset = div_s64(offset64 << NTP_SCALE_SHIFT, NTP_INTERVAL_FREQ);
+	ntp_set_time_offset(ntpdata - tk_ntp_data, offset64);
 }
 
 static void __ntp_clear(struct ntp_data *ntpdata)
@@ -390,6 +390,24 @@ s64 ntp_drain_time_offset(unsigned int tkid, s64 amount)
 	return 0;
 }
 
+/**
+ * ntp_set_time_offset - Set the NTP time offset (phase correction)
+ * @tkid:	Timekeeper ID
+ * @offset_ns:	Desired offset in nanoseconds
+ *
+ * Converts nanoseconds to internal time_offset units and stores it.
+ * Also clears time_adjust since a new offset supersedes any pending
+ * adjtime() slew.
+ */
+void ntp_set_time_offset(unsigned int tkid, s64 offset_ns)
+{
+	struct ntp_data *ntpdata = &tk_ntp_data[tkid];
+
+	ntpdata->time_offset = div_s64((s64)offset_ns << NTP_SCALE_SHIFT,
+				       NTP_INTERVAL_FREQ);
+	ntpdata->time_adjust = 0;
+}
+
 /**
  * ntp_get_next_leap - Returns the next leapsecond in CLOCK_REALTIME ktime_t
  * @tkid:	Timekeeper ID
@@ -498,26 +516,22 @@ int second_overflow(unsigned int tkid, time64_t secs)
 	/* Check PPS signal */
 	pps_dec_valid(ntpdata);
 
-	if (!ntpdata->time_adjust)
-		goto out;
-
-	if (ntpdata->time_adjust > MAX_TICKADJ) {
-		ntpdata->time_adjust -= MAX_TICKADJ;
-		ntpdata->tick_length += MAX_TICKADJ_SCALED;
-		goto out;
-	}
-
-	if (ntpdata->time_adjust < -MAX_TICKADJ) {
-		ntpdata->time_adjust += MAX_TICKADJ;
-		ntpdata->tick_length -= MAX_TICKADJ_SCALED;
-		goto out;
+	/*
+	 * Fold any pending time_adjust (from adjtime()) into time_offset.
+	 * This used to inflate tick_length directly; now it uses the same
+	 * per-tick skew mechanism as NTP's time_offset. Rate-limited to
+	 * MAX_TICKADJ (500µs) per second.
+	 */
+	if (ntpdata->time_adjust) {
+		long adj = clamp(ntpdata->time_adjust,
+				 (long)-MAX_TICKADJ, (long)MAX_TICKADJ);
+
+		ntpdata->time_adjust -= adj;
+		ntpdata->time_offset += div_s64(
+			(s64)adj * NSEC_PER_USEC << NTP_SCALE_SHIFT,
+			NTP_INTERVAL_FREQ);
 	}
 
-	ntpdata->tick_length += (s64)(ntpdata->time_adjust * NSEC_PER_USEC / NTP_INTERVAL_FREQ)
-				<< NTP_SCALE_SHIFT;
-	ntpdata->time_adjust = 0;
-
-out:
 	return leap;
 }
 
diff --git a/kernel/time/ntp_internal.h b/kernel/time/ntp_internal.h
index 05e5dd5e1b70..639860ff2baf 100644
--- a/kernel/time/ntp_internal.h
+++ b/kernel/time/ntp_internal.h
@@ -8,6 +8,7 @@ extern void ntp_clear(unsigned int tkid);
 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 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,
-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [RFC PATCH v3 07/10] ntp: Remove tick_length_base, use tick_length directly
  2026-05-20 13:33 [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
                   ` (5 preceding siblings ...)
  2026-05-20 13:33 ` [RFC PATCH v3 06/10] ntp: Convert adjtime() to use time_offset instead of tick_length inflation David Woodhouse
@ 2026-05-20 13:33 ` David Woodhouse
  2026-05-20 13:33 ` [RFC PATCH v3 08/10] timekeeping: Add absolute reference for feed-forward clock discipline David Woodhouse
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 21+ messages in thread
From: David Woodhouse @ 2026-05-20 13:33 UTC (permalink / raw)
  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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel
  Cc: David Woodhouse

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

Now that nothing inflates tick_length beyond tick_length_base (the
adjtime path was converted to use time_offset in the previous commit),
the two fields are always equal.

Remove tick_length_base and keep tick_length as the single field.
Remove the per-second reset and the delta update in
ntp_update_frequency() since there is no separate base to track.

No functional change intended.

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

diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index 2b75653b456c..4494f258dd86 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -26,8 +26,7 @@
 /**
  * struct ntp_data - Structure holding all NTP related state
  * @tick_usec:		USER_HZ period in microseconds
- * @tick_length:	Adjusted tick length
- * @tick_length_base:	Base value for @tick_length
+ * @tick_length:	Tick length in ns << NTP_SCALE_SHIFT
  * @time_state:		State of the clock synchronization
  * @time_status:	Clock status bits
  * @time_offset:	Time adjustment in nanoseconds
@@ -59,7 +58,6 @@
 struct ntp_data {
 	unsigned long		tick_usec;
 	u64			tick_length;
-	u64			tick_length_base;
 	int			time_state;
 	int			time_status;
 	s64			time_offset;
@@ -246,8 +244,7 @@ static inline void pps_fill_timex(struct ntp_data *ntpdata, struct __kernel_time
 #endif /* CONFIG_NTP_PPS */
 
 /*
- * Update tick_length and tick_length_base, based on tick_usec, ntp_tick_adj and
- * time_freq:
+ * Update tick_length based on tick_usec, ntp_tick_adj and time_freq:
  */
 static void ntp_update_frequency(struct ntp_data *ntpdata)
 {
@@ -264,8 +261,7 @@ static void ntp_update_frequency(struct ntp_data *ntpdata)
 	 * Don't wait for the next second_overflow, apply the change to the
 	 * tick length immediately:
 	 */
-	ntpdata->tick_length		+= new_base - ntpdata->tick_length_base;
-	ntpdata->tick_length_base	 = new_base;
+	ntpdata->tick_length	 = new_base;
 }
 
 static inline s64 ntp_update_offset_fll(struct ntp_data *ntpdata, s64 offset64, long secs)
@@ -342,7 +338,6 @@ static void __ntp_clear(struct ntp_data *ntpdata)
 
 	ntp_update_frequency(ntpdata);
 
-	ntpdata->tick_length	= ntpdata->tick_length_base;
 	ntpdata->time_offset	= 0;
 
 	ntpdata->ntp_next_leap_sec = TIME64_MAX;
@@ -502,7 +497,6 @@ int second_overflow(unsigned int tkid, time64_t secs)
 	}
 
 	/* Compute the phase adjustment for the next second */
-	ntpdata->tick_length	 = ntpdata->tick_length_base;
 
 	/*
 	 * Set the per-tick skew rate for the tick code. This is in the
-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [RFC PATCH v3 08/10] timekeeping: Add absolute reference for feed-forward clock discipline
  2026-05-20 13:33 [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
                   ` (6 preceding siblings ...)
  2026-05-20 13:33 ` [RFC PATCH v3 07/10] ntp: Remove tick_length_base, use tick_length directly David Woodhouse
@ 2026-05-20 13:33 ` David Woodhouse
  2026-05-20 13:33 ` [RFC PATCH v3 09/10] ptp_vmclock: Feed reference to timekeeping for feed-forward discipline David Woodhouse
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 21+ messages in thread
From: David Woodhouse @ 2026-05-20 13:33 UTC (permalink / raw)
  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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel
  Cc: David Woodhouse

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

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 <dwmw@amazon.co.uk>
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 <linux/clocksource_ids.h>
+#include <linux/types.h>
+
+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 <linux/timekeeping_reference.h>
+
+
+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


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [RFC PATCH v3 09/10] ptp_vmclock: Feed reference to timekeeping for feed-forward discipline
  2026-05-20 13:33 [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
                   ` (7 preceding siblings ...)
  2026-05-20 13:33 ` [RFC PATCH v3 08/10] timekeeping: Add absolute reference for feed-forward clock discipline David Woodhouse
@ 2026-05-20 13:33 ` David Woodhouse
  2026-05-20 13:33 ` [RFC PATCH v3 10/10] kernel/time: Add /dev/vmclock_host miscdev David Woodhouse
  2026-05-23 11:49 ` [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
  10 siblings, 0 replies; 21+ messages in thread
From: David Woodhouse @ 2026-05-20 13:33 UTC (permalink / raw)
  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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel
  Cc: David Woodhouse

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

When a vmclock device provides valid time, call timekeeping_set_reference()
to enable feed-forward clock discipline. This eliminates drift between the
system clock and the vmclock reference.

The reference is set at probe time (after PTP registration) and updated
on each notification from the hypervisor (ACPI or DT interrupt).

If cycle_interval is not provided (set to 0), timekeeping_set_reference()
fills it from the current timekeeper.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: Kiro:claude-opus-4.6-1m
---
 drivers/ptp/ptp_vmclock.c | 95 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 95 insertions(+)

diff --git a/drivers/ptp/ptp_vmclock.c b/drivers/ptp/ptp_vmclock.c
index 8b630eb916b5..abb8f821bcaa 100644
--- a/drivers/ptp/ptp_vmclock.c
+++ b/drivers/ptp/ptp_vmclock.c
@@ -27,6 +27,7 @@
 #include <uapi/linux/vmclock-abi.h>
 
 #include <linux/ptp_clock_kernel.h>
+#include <linux/timekeeping_reference.h>
 
 #ifdef CONFIG_X86
 #include <asm/pvclock.h>
@@ -48,6 +49,7 @@ struct vmclock_state {
 	wait_queue_head_t disrupt_wait;
 	struct ptp_clock_info ptp_clock_info;
 	struct ptp_clock *ptp_clock;
+	struct timer_list cmp_timer;
 	enum clocksource_ids cs_id, sys_cs_id;
 	int index;
 	char *name;
@@ -334,6 +336,92 @@ static const struct ptp_clock_info ptp_vmclock_info = {
 	.getcrosststamp = ptp_vmclock_getcrosststamp,
 };
 
+static void vmclock_cmp_timer_fn(struct timer_list *t)
+{
+	struct vmclock_state *st = container_of(t, struct vmclock_state, cmp_timer);
+	struct vmclock_abi *clk = st->clk;
+	struct system_time_snapshot snap;
+	unsigned __int128 product;
+	u64 delta, ref_frac, ref_ns, sys_ns;
+	s64 diff;
+	u32 seq;
+
+	do {
+		seq = le32_to_cpu(READ_ONCE(clk->seq_count));
+		if (seq & 1)
+			goto rearm;
+		/* Pairs with the smp_wmb() in the vmclock page writer */
+		smp_rmb();
+
+		ktime_get_snapshot(&snap);
+		if (snap.cs_id != st->cs_id)
+			goto rearm;
+
+		delta = snap.cycles - le64_to_cpu(clk->counter_value);
+		product = (unsigned __int128)delta *
+			  le64_to_cpu(clk->counter_period_frac_sec);
+		product >>= clk->counter_period_shift;
+		product += le64_to_cpu(clk->time_frac_sec);
+		ref_frac = (u64)product;
+		ref_ns = mul_u64_u64_shr(ref_frac, NSEC_PER_SEC, 64);
+		ref_ns += (le64_to_cpu(clk->time_sec) +
+			   (u64)(product >> 64)) * NSEC_PER_SEC;
+		/* Pairs with the smp_wmb() in the vmclock page writer */
+		smp_rmb();
+		if (seq != le32_to_cpu(READ_ONCE(clk->seq_count)))
+			goto rearm;
+	} while (0);
+
+	sys_ns = ktime_to_ns(snap.real) -
+		 (s64)(int16_t)le16_to_cpu(clk->tai_offset_sec) * NSEC_PER_SEC;
+	diff = (s64)(ref_ns - sys_ns);
+	pr_info("vmclock_cmp: diff=%lldns tsc=%llx\n", diff, snap.cycles);
+
+rearm:
+	mod_timer(&st->cmp_timer, jiffies + msecs_to_jiffies(500));
+}
+
+static void vmclock_set_tk_reference(struct vmclock_state *st)
+{
+	struct vmclock_abi *clk = st->clk;
+	struct tk_reference ref = {
+		.cs_id = st->cs_id,
+		.counter_value = le64_to_cpu(clk->counter_value),
+		.time_sec = le64_to_cpu(clk->time_sec),
+		.time_frac_sec = le64_to_cpu(clk->time_frac_sec),
+		.period_frac_sec = le64_to_cpu(clk->counter_period_frac_sec),
+		.period_shift = clk->counter_period_shift,
+	};
+
+	/* Convert TAI to UTC for comparison with xtime_sec */
+	if (clk->time_type == VMCLOCK_TIME_TAI &&
+	    (le64_to_cpu(clk->flags) & VMCLOCK_FLAG_TAI_OFFSET_VALID))
+		ref.time_sec += (int16_t)le16_to_cpu(clk->tai_offset_sec);
+
+	if (clk->clock_status != VMCLOCK_STATUS_UNRELIABLE) {
+		/* Step clock if far from reference */
+		struct timespec64 now, vmtime;
+		unsigned __int128 product;
+		u64 cycles = get_cycles();
+		u64 delta_cycles = cycles - ref.counter_value;
+		s64 delta_ns;
+
+		product = (unsigned __int128)delta_cycles * ref.period_frac_sec;
+		product >>= ref.period_shift;
+		product += ref.time_frac_sec;
+		vmtime.tv_sec = ref.time_sec + (u64)(product >> 64);
+		vmtime.tv_nsec = mul_u64_u64_shr((u64)product,
+						  NSEC_PER_SEC, 64);
+
+		ktime_get_real_ts64(&now);
+		delta_ns = timespec64_to_ns(&vmtime) - timespec64_to_ns(&now);
+		if (delta_ns > 100000000 || delta_ns < -100000000)
+			do_settimeofday64(&vmtime);
+
+		timekeeping_set_reference(&ref);
+	}
+}
+
 static struct ptp_clock *vmclock_ptp_register(struct device *dev,
 					      struct vmclock_state *st)
 {
@@ -525,6 +613,7 @@ vmclock_acpi_notification_handler(acpi_handle __always_unused handle,
 	struct device *device = dev;
 	struct vmclock_state *st = device->driver_data;
 
+	vmclock_set_tk_reference(st);
 	wake_up_interruptible(&st->disrupt_wait);
 }
 
@@ -580,6 +669,7 @@ static irqreturn_t vmclock_of_irq_handler(int __always_unused irq, void *_st)
 {
 	struct vmclock_state *st = _st;
 
+	vmclock_set_tk_reference(st);
 	wake_up_interruptible(&st->disrupt_wait);
 	return IRQ_HANDLED;
 }
@@ -751,8 +841,13 @@ static int vmclock_probe(struct platform_device *pdev)
 			st->ptp_clock = NULL;
 			return ret;
 		}
+		if (st->ptp_clock)
+			vmclock_set_tk_reference(st);
 	}
 
+	timer_setup(&st->cmp_timer, vmclock_cmp_timer_fn, 0);
+	mod_timer(&st->cmp_timer, jiffies + msecs_to_jiffies(500));
+
 	if (!st->miscdev.minor && !st->ptp_clock) {
 		/* Neither miscdev nor PTP registered */
 		dev_info(dev, "vmclock: Neither miscdev nor PTP available; not registering\n");
-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [RFC PATCH v3 10/10] kernel/time: Add /dev/vmclock_host miscdev
  2026-05-20 13:33 [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
                   ` (8 preceding siblings ...)
  2026-05-20 13:33 ` [RFC PATCH v3 09/10] ptp_vmclock: Feed reference to timekeeping for feed-forward discipline David Woodhouse
@ 2026-05-20 13:33 ` David Woodhouse
  2026-05-21  6:29   ` Wen Gu
  2026-05-23 11:49 ` [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
  10 siblings, 1 reply; 21+ messages in thread
From: David Woodhouse @ 2026-05-20 13:33 UTC (permalink / raw)
  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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel
  Cc: David Woodhouse

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

Expose the host's NTP-disciplined clock as a vmclock_abi page via
/dev/vmclock_host. A VMM can mmap or poll() this device to obtain
precision time parameters for relaying to guests.

The page is updated via the pvclock_gtod notifier chain when the NTP
frequency or skew rate changes. The period computation is redone on
frequency changes; time tuple updates are cheap. The phase offset
(time_offset) is computed under tk_core.lock in
timekeeping_set_reference(), matching the locking used by
do_adjtimex/hardpps.

Fields populated:
- counter_id: X86_TSC (or ARM_VCNT)
- time_type: TAI (if tai_offset known) or UTC
- counter_value: TSC at reference point
- time_sec/time_frac_sec: time at reference point
- counter_period_frac_sec: NTP-disciplined TSC period
- tai_offset_sec: current UTC-TAI offset (if known)
- clock_status: SYNCHRONIZED / FREERUNNING / UNKNOWN
- leap_indicator: from NTP time_state

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: Kiro:claude-opus-4.6-1m
---
 include/linux/timekeeper_internal.h           |   2 +
 kernel/time/Kconfig                           |   7 +
 kernel/time/Makefile                          |   1 +
 kernel/time/ntp.c                             |  19 +
 kernel/time/ntp_internal.h                    |   5 +
 kernel/time/timekeeping.c                     |   2 +
 kernel/time/vmclock_host.c                    | 391 ++++++++++++++++++
 .../selftests/timers/vmclock_host_test.c      | 171 ++++++++
 8 files changed, 598 insertions(+)
 create mode 100644 kernel/time/vmclock_host.c
 create mode 100644 tools/testing/selftests/timers/vmclock_host_test.c

diff --git a/include/linux/timekeeper_internal.h b/include/linux/timekeeper_internal.h
index 9de6b5b94dc0..c3d6f17e0623 100644
--- a/include/linux/timekeeper_internal.h
+++ b/include/linux/timekeeper_internal.h
@@ -185,6 +185,8 @@ struct timekeeper {
 	u32			ntp_err_mult;
 	u32			skip_second_overflow;
 	s64			skew_delta;
+	int			ntp_status;
+	int			ntp_time_state;
 	s32			tai_offset;
 };
 
diff --git a/kernel/time/Kconfig b/kernel/time/Kconfig
index 02aac7c5aa76..f0cddfec5751 100644
--- a/kernel/time/Kconfig
+++ b/kernel/time/Kconfig
@@ -221,4 +221,11 @@ config POSIX_AUX_CLOCKS
 	  and other clock domains, which are not correlated to the TAI/NTP
 	  notion of time.
 
+config VMCLOCK_HOST
+	tristate "VMClock host time provider (/dev/vmclock_host)"
+	depends on X86_TSC || ARM64
+	help
+	  Expose the host NTP-disciplined clock as a vmclock page via
+	  /dev/vmclock_host for VMMs to relay precision time to guests.
+
 endmenu
diff --git a/kernel/time/Makefile b/kernel/time/Makefile
index eaf290c972f9..549070254e3a 100644
--- a/kernel/time/Makefile
+++ b/kernel/time/Makefile
@@ -33,3 +33,4 @@ obj-$(CONFIG_TIME_NS)				+= namespace.o
 obj-$(CONFIG_TIME_NS_VDSO)			+= namespace_vdso.o
 obj-$(CONFIG_TEST_CLOCKSOURCE_WATCHDOG)		+= clocksource-wdtest.o
 obj-$(CONFIG_TIME_KUNIT_TEST)			+= time_test.o
+obj-$(CONFIG_VMCLOCK_HOST)	+= vmclock_host.o
diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index 3dc098695665..2866d4208117 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -365,6 +365,13 @@ s64 ntp_get_skew_delta(unsigned int tkid)
 	return tk_ntp_data[tkid].skew_delta;
 }
 
+s64 ntp_get_time_offset_ns(unsigned int tkid)
+{
+	return shift_right(tk_ntp_data[tkid].time_offset * NTP_INTERVAL_FREQ,
+			   NTP_SCALE_SHIFT);
+}
+EXPORT_SYMBOL_GPL(ntp_get_time_offset_ns);
+
 s64 ntp_drain_time_offset(unsigned int tkid, s64 amount)
 {
 	struct ntp_data *ntpdata = &tk_ntp_data[tkid];
@@ -669,6 +676,18 @@ static inline bool ntp_synced(void)
 	return !(tk_ntp_data[TIMEKEEPER_CORE].time_status & STA_UNSYNC);
 }
 
+int ntp_get_status(void)
+{
+	return tk_ntp_data[TIMEKEEPER_CORE].time_status;
+}
+EXPORT_SYMBOL_GPL(ntp_get_status);
+
+int ntp_get_time_state(void)
+{
+	return tk_ntp_data[TIMEKEEPER_CORE].time_state;
+}
+EXPORT_SYMBOL_GPL(ntp_get_time_state);
+
 /*
  * If we have an externally synchronized Linux clock, then update RTC clock
  * accordingly every ~11 minutes. Generally RTCs can only store second
diff --git a/kernel/time/ntp_internal.h b/kernel/time/ntp_internal.h
index 14ca8bc08120..ba1d14bbcf0e 100644
--- a/kernel/time/ntp_internal.h
+++ b/kernel/time/ntp_internal.h
@@ -2,13 +2,18 @@
 #ifndef _LINUX_NTP_INTERNAL_H
 #define _LINUX_NTP_INTERNAL_H
 
+struct audit_ntp_data;
+
 extern void ntp_init(void);
+extern int ntp_get_status(void);
+extern int ntp_get_time_state(void);
 extern void ntp_clear(unsigned int tkid);
 /* Returns how long ticks are at present, in ns / 2^NTP_SCALE_SHIFT. */
 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 s64 ntp_get_time_offset_ns(unsigned int tkid);
 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);
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index 5c4b377505bc..b93fab0890df 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -2380,6 +2380,8 @@ static void timekeeping_adjust(struct timekeeper *tk, s64 offset)
 			return;
 		tk->ntp_tick = ntp_tl;
 		tk->skew_delta = skew;
+		tk->ntp_status = ntp_get_status();
+		tk->ntp_time_state = ntp_get_time_state();
 		mult = div64_u64((tk->ntp_tick + skew) >> tk->ntp_error_shift,
 				  tk->cycle_interval);
 	}
diff --git a/kernel/time/vmclock_host.c b/kernel/time/vmclock_host.c
new file mode 100644
index 000000000000..d43f2b043fb9
--- /dev/null
+++ b/kernel/time/vmclock_host.c
@@ -0,0 +1,391 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * /dev/vmclock_host - Expose host NTP-disciplined time as a vmclock page.
+ *
+ * This provides a vmclock_abi structure populated from the host's
+ * CLOCK_REALTIME (TAI), allowing a VMM to efficiently relay precision
+ * time to guests without per-tick overhead.
+ *
+ * The page is updated only when the NTP frequency (ntp_tick) changes
+ * or the clocksource changes — not on every timekeeping tick.
+ * Userspace can poll() for changes.
+ *
+ * Copyright © 2026 Amazon.com, Inc. or its affiliates.
+ */
+
+#include <linux/clocksource_ids.h>
+#include <linux/pvclock_gtod.h>
+#include <linux/timex.h>
+#include "ntp_internal.h"
+#include <linux/timekeeping.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/timekeeper_internal.h>
+#include <linux/wait.h>
+
+#include <uapi/linux/vmclock-abi.h>
+
+
+static struct vmclock_abi *vmclock_page;
+static DECLARE_WAIT_QUEUE_HEAD(vmclock_wait);
+static u64 cached_ntp_tick;
+static s64 cached_skew_delta;
+static u64 cached_period_frac;
+static u8 cached_period_shift;
+
+/*
+ * Compute counter_period_frac_sec from ntp_tick and cycle_interval.
+ *
+ * ntp_tick is ns_per_tick << 32.
+ * cycle_interval is counter cycles per tick.
+ *
+ * vmclock wants: period = frac_sec / 2^(64 + shift) in seconds.
+ *
+ * ns_per_cycle = ntp_tick / cycle_interval (in <<32 fixed point)
+ *
+ * period = ntp_tick / (cycle_interval * 10^9 * 2^32) seconds/cycle
+ * frac_sec = ntp_tick * 2^(32+shift) / (cycle_interval * 10^9)
+ *
+ * Use div64_u64 with maximum pre-shift for precision.
+ * The key: do TWO divisions to get 64 bits of quotient.
+ */
+static void vmclock_compute_period(struct timekeeper *tk,
+				   u64 *period_frac, u8 *period_shift)
+{
+	u64 ntp_tick = tk->ntp_tick;
+	u64 cycle_interval = tk->cycle_interval;
+	u64 divisor = cycle_interval * 1000000000ULL;
+	int headroom = __builtin_clzll(ntp_tick);
+	u64 rem, result;
+	int bits_so_far;
+
+	/*
+	 * Compute ntp_tick * 2^(headroom + N) / divisor with 64 bits
+	 * of precision, using iterative 32-bit chunk divisions.
+	 *
+	 * First division: ntp_tick << headroom / divisor
+	 */
+	result = div64_u64_rem(ntp_tick << headroom, divisor, &rem);
+	bits_so_far = 64 - __builtin_clzll(result ?: 1);
+
+	/* Fill remaining bits 32 at a time from the remainder */
+	while (bits_so_far < 64 && rem) {
+		int chunk = min(32, 64 - bits_so_far);
+		int rem_headroom = __builtin_clzll(rem);
+		u64 extra;
+
+		if (rem_headroom < chunk)
+			chunk = rem_headroom;
+
+		extra = div64_u64_rem(rem << chunk, divisor, &rem);
+		result = (result << chunk) | extra;
+		bits_so_far += chunk;
+		headroom += chunk;
+	}
+
+	/* Pad with zeros if we ran out of remainder */
+	if (bits_so_far < 64) {
+		result <<= (64 - bits_so_far);
+		headroom += (64 - bits_so_far);
+	}
+
+	/*
+	 * result = ntp_tick * 2^headroom / divisor
+	 *        = (ntp_tick / (cycle_interval * 10^9)) * 2^headroom
+	 *        = period_seconds * 2^32 * 2^headroom
+	 *        = period_seconds * 2^(32 + headroom)
+	 *
+	 * vmclock: frac_sec / 2^(64 + shift) = period_seconds
+	 * So: shift = 32 + headroom - 64 = headroom - 32
+	 */
+	*period_frac = result;
+	*period_shift = (u8)(headroom - 32);
+}
+
+
+static u8 vmclock_counter_id(struct timekeeper *tk)
+{
+	enum clocksource_ids id = tk->cs_id;
+
+	if (IS_ENABLED(CONFIG_X86) && id == CSID_X86_TSC)
+		return VMCLOCK_COUNTER_X86_TSC;
+	if (IS_ENABLED(CONFIG_ARM64) && id == CSID_ARM_ARCH_COUNTER)
+		return VMCLOCK_COUNTER_ARM_VCNT;
+	return VMCLOCK_COUNTER_INVALID;
+}
+
+/*
+ * Called from pvclock_gtod_notify on every timekeeping update.
+ * Only does real work when ntp_tick or skew_delta changes.
+ */
+static int vmclock_host_notify(struct notifier_block *nb,
+			       unsigned long was_set, void *data)
+{
+	struct timekeeper *tk = data;
+	struct vmclock_abi *clk = vmclock_page;
+	bool period_changed = false;
+	u8 counter_id;
+	s64 ns, sec;
+	u64 hi, rem, counter_value, time_frac;
+	__le64 le_time_sec, le_time_frac, le_counter_value;
+	__le64 le_period_frac;
+	u8 period_shift, clock_status;
+
+	if (!clk)
+		return NOTIFY_DONE;
+
+	/* Early exit if nothing relevant changed */
+	if (clk->clock_status != VMCLOCK_STATUS_UNKNOWN &&
+	    tk->ntp_tick == cached_ntp_tick &&
+	    tk->skew_delta == cached_skew_delta && !was_set)
+		return NOTIFY_DONE;
+
+	counter_id = vmclock_counter_id(tk);
+	if (counter_id == VMCLOCK_COUNTER_INVALID) {
+		/* Invalidate the page if clocksource isn't usable */
+		WRITE_ONCE(clk->seq_count, cpu_to_le32(
+			le32_to_cpu(READ_ONCE(clk->seq_count)) + 1));
+		smp_wmb();
+		clk->counter_id = VMCLOCK_COUNTER_INVALID;
+		clk->clock_status = VMCLOCK_STATUS_UNKNOWN;
+		smp_wmb();
+		WRITE_ONCE(clk->seq_count, cpu_to_le32(
+			le32_to_cpu(READ_ONCE(clk->seq_count)) + 1));
+		return NOTIFY_DONE;
+	}
+
+	/* Recompute period only when frequency changes */
+	if (tk->ntp_tick != cached_ntp_tick) {
+		vmclock_compute_period(tk, &cached_period_frac,
+				       &cached_period_shift);
+		cached_ntp_tick = tk->ntp_tick;
+		period_changed = true;
+	}
+	cached_skew_delta = tk->skew_delta;
+
+	/* Compute time tuple: C = A + ntp_error + time_offset */
+	ns = tk->tkr_mono.xtime_nsec >> tk->tkr_mono.shift;
+	sec = tk->xtime_sec + tk->tai_offset;
+	ns += tk->ntp_error >> (tk->tkr_mono.shift + tk->ntp_error_shift);
+	ns += ntp_get_time_offset_ns(tk->id);
+
+	while (ns < 0) {
+		ns += NSEC_PER_SEC;
+		sec--;
+	}
+	while (ns >= NSEC_PER_SEC) {
+		ns -= NSEC_PER_SEC;
+		sec++;
+	}
+
+	counter_value = tk->tkr_mono.cycle_last;
+	hi = div64_u64_rem((u64)ns << 32, 1000000000ULL, &rem);
+	time_frac = (hi << 32) | div64_u64(rem << 32, 1000000000ULL);
+
+	clock_status = !(ntp_get_status() & STA_UNSYNC) ?
+		VMCLOCK_STATUS_SYNCHRONIZED : VMCLOCK_STATUS_FREERUNNING;
+
+	/* Prepare le values */
+	le_counter_value = cpu_to_le64(counter_value);
+	le_time_sec = cpu_to_le64(sec);
+	le_time_frac = cpu_to_le64(time_frac);
+	le_period_frac = cpu_to_le64(cached_period_frac);
+	period_shift = cached_period_shift;
+
+	/* Update page under seqcount */
+	WRITE_ONCE(clk->seq_count, cpu_to_le32(
+		le32_to_cpu(READ_ONCE(clk->seq_count)) + 1));
+	smp_wmb();
+
+	clk->counter_id = counter_id;
+	clk->counter_value = le_counter_value;
+	clk->time_sec = le_time_sec;
+	clk->time_frac_sec = le_time_frac;
+	if (period_changed) {
+		clk->counter_period_frac_sec = le_period_frac;
+		clk->counter_period_shift = period_shift;
+	}
+	clk->clock_status = clock_status;
+
+	/* Set leap second indicator from NTP time_state */
+	switch (ntp_get_time_state()) {
+	case TIME_INS:
+		clk->leap_indicator = VMCLOCK_LEAP_PRE_POS;
+		break;
+	case TIME_DEL:
+		clk->leap_indicator = VMCLOCK_LEAP_PRE_NEG;
+		break;
+	case TIME_OOP:
+		clk->leap_indicator = VMCLOCK_LEAP_POS;
+		break;
+	case TIME_WAIT:
+		clk->leap_indicator = (ntp_get_status() & STA_DEL) ?
+			VMCLOCK_LEAP_POST_NEG : VMCLOCK_LEAP_POST_POS;
+		break;
+	default:
+		clk->leap_indicator = VMCLOCK_LEAP_NONE;
+		break;
+	}
+
+	/* Export as TAI if tai_offset is known, otherwise UTC */
+	if (tk->tai_offset) {
+		clk->time_type = VMCLOCK_TIME_TAI;
+		clk->tai_offset_sec = cpu_to_le16((s16)tk->tai_offset);
+		clk->flags = cpu_to_le64(VMCLOCK_FLAG_TAI_OFFSET_VALID |
+					 VMCLOCK_FLAG_TIME_MONOTONIC |
+					 VMCLOCK_FLAG_NOTIFICATION_PRESENT);
+	} else {
+		clk->time_type = VMCLOCK_TIME_UTC;
+		clk->tai_offset_sec = 0;
+		clk->flags = cpu_to_le64(VMCLOCK_FLAG_TIME_MONOTONIC |
+					 VMCLOCK_FLAG_NOTIFICATION_PRESENT);
+	}
+
+	smp_wmb();
+	WRITE_ONCE(clk->seq_count, cpu_to_le32(
+		le32_to_cpu(READ_ONCE(clk->seq_count)) + 1));
+
+	wake_up_interruptible(&vmclock_wait);
+	return NOTIFY_DONE;
+}
+
+/* File operations */
+
+struct vmclock_host_file {
+	u32 last_seq;
+};
+
+static int vmclock_host_open(struct inode *inode, struct file *fp)
+{
+	struct vmclock_host_file *fst;
+
+	fst = kzalloc(sizeof(*fst), GFP_KERNEL);
+	if (!fst)
+		return -ENOMEM;
+
+	fp->private_data = fst;
+	return 0;
+}
+
+static int vmclock_host_release(struct inode *inode, struct file *fp)
+{
+	kfree(fp->private_data);
+	return 0;
+}
+
+static int vmclock_host_mmap(struct file *fp, struct vm_area_struct *vma)
+{
+	if ((vma->vm_flags & (VM_READ | VM_WRITE)) != VM_READ)
+		return -EROFS;
+
+	if (vma->vm_end - vma->vm_start != PAGE_SIZE || vma->vm_pgoff)
+		return -EINVAL;
+
+	return remap_pfn_range(vma, vma->vm_start,
+			       virt_to_phys(vmclock_page) >> PAGE_SHIFT,
+			       PAGE_SIZE, vma->vm_page_prot);
+}
+
+static ssize_t vmclock_host_read(struct file *fp, char __user *buf,
+				 size_t count, loff_t *ppos)
+{
+	struct vmclock_host_file *fst = fp->private_data;
+	u32 seq;
+
+	if (*ppos >= PAGE_SIZE)
+		return 0;
+	if (count > PAGE_SIZE - *ppos)
+		count = PAGE_SIZE - *ppos;
+
+	do {
+		seq = le32_to_cpu(READ_ONCE(vmclock_page->seq_count));
+		if (seq & 1) {
+			cpu_relax();
+			continue;
+		}
+		smp_rmb();
+		if (copy_to_user(buf, (char *)vmclock_page + *ppos, count))
+			return -EFAULT;
+		smp_rmb();
+	} while (le32_to_cpu(READ_ONCE(vmclock_page->seq_count)) != seq);
+
+	fst->last_seq = seq;
+	*ppos += count;
+	return count;
+}
+
+static __poll_t vmclock_host_poll(struct file *fp, poll_table *wait)
+{
+	struct vmclock_host_file *fst = fp->private_data;
+	u32 seq;
+
+	poll_wait(fp, &vmclock_wait, wait);
+
+	seq = le32_to_cpu(READ_ONCE(vmclock_page->seq_count));
+	if (fst->last_seq != seq)
+		return EPOLLIN | EPOLLRDNORM;
+
+	return 0;
+}
+
+static const struct file_operations vmclock_host_fops = {
+	.owner = THIS_MODULE,
+	.open = vmclock_host_open,
+	.release = vmclock_host_release,
+	.mmap = vmclock_host_mmap,
+	.read = vmclock_host_read,
+	.poll = vmclock_host_poll,
+};
+
+static struct miscdevice vmclock_host_miscdev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "vmclock_host",
+	.fops = &vmclock_host_fops,
+};
+
+static struct notifier_block vmclock_host_nb = {
+	.notifier_call = vmclock_host_notify,
+};
+
+static int __init vmclock_host_init(void)
+{
+	int ret;
+
+	vmclock_page = (struct vmclock_abi *)get_zeroed_page(GFP_KERNEL);
+	if (!vmclock_page)
+		return -ENOMEM;
+
+	/* Set constant fields */
+	vmclock_page->magic = cpu_to_le32(VMCLOCK_MAGIC);
+	vmclock_page->size = cpu_to_le32(PAGE_SIZE);
+	vmclock_page->version = cpu_to_le16(1);
+
+	ret = misc_register(&vmclock_host_miscdev);
+	if (ret) {
+		free_page((unsigned long)vmclock_page);
+		vmclock_page = NULL;
+		return ret;
+	}
+
+	pvclock_gtod_register_notifier(&vmclock_host_nb);
+	pr_info("vmclock_host: registered /dev/vmclock_host\n");
+	return 0;
+}
+
+static void __exit vmclock_host_exit(void)
+{
+	pvclock_gtod_unregister_notifier(&vmclock_host_nb);
+	misc_deregister(&vmclock_host_miscdev);
+	free_page((unsigned long)vmclock_page);
+	vmclock_page = NULL;
+}
+
+module_init(vmclock_host_init);
+module_exit(vmclock_host_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw@amazon.co.uk>");
+MODULE_DESCRIPTION("VMClock host time provider");
diff --git a/tools/testing/selftests/timers/vmclock_host_test.c b/tools/testing/selftests/timers/vmclock_host_test.c
new file mode 100644
index 000000000000..c83cc7e6d404
--- /dev/null
+++ b/tools/testing/selftests/timers/vmclock_host_test.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test /dev/vmclock_host by comparing its time against CLOCK_TAI.
+ *
+ * Maps the vmclock page, reads time from it using the ABI formula,
+ * and compares with clock_gettime(CLOCK_TAI) using ABA timestamps
+ * to bound the uncertainty.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <linux/vmclock-abi.h>
+
+#ifdef __x86_64__
+static inline uint64_t read_counter(void)
+{
+	unsigned int lo, hi;
+	asm volatile("rdtsc" : "=a"(lo), "=d"(hi));
+	return ((uint64_t)hi << 32) | lo;
+}
+#elif defined(__aarch64__)
+static inline uint64_t read_counter(void)
+{
+	uint64_t val;
+	asm volatile("mrs %0, cntvct_el0" : "=r"(val));
+	return val;
+}
+#else
+#error "Unsupported architecture"
+#endif
+
+/*
+ * Compute time from vmclock: T = time_sec + time_frac_sec/2^64 +
+ *   (counter_now - counter_value) * counter_period_frac_sec >> (64 + shift)
+ *
+ * Returns nanoseconds since epoch.
+ */
+static int64_t vmclock_read_ns(const volatile struct vmclock_abi *clk,
+			       uint64_t counter_now)
+{
+	uint64_t delta = counter_now - clk->counter_value;
+	uint64_t period = clk->counter_period_frac_sec;
+	uint8_t shift = clk->counter_period_shift;
+	__uint128_t ns128;
+
+	/* delta * period gives seconds in 0.(64+shift) fixed point */
+	ns128 = (__uint128_t)delta * period;
+	ns128 >>= shift;
+	/* Now ns128 is seconds in 0.64 fixed point. Add time_frac_sec */
+	ns128 += clk->time_frac_sec;
+	/* Top 64 bits are whole seconds of fractional part — but we
+	 * need to add time_sec for the full result */
+	uint64_t frac_sec = (uint64_t)(ns128 >> 64);
+	uint64_t sub_sec_ns = (uint64_t)(((ns128 & 0xFFFFFFFFFFFFFFFFULL) *
+					   1000000000ULL) >> 64);
+
+	return (int64_t)(clk->time_sec + frac_sec) * 1000000000LL + sub_sec_ns;
+}
+
+static int64_t clock_tai_ns(void)
+{
+	struct timespec ts;
+	clock_gettime(CLOCK_TAI, &ts);
+	return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec;
+}
+
+int main(void)
+{
+	int fd, ret = 0;
+	volatile struct vmclock_abi *clk;
+	int i, failures = 0;
+
+	fd = open("/dev/vmclock_host", O_RDONLY);
+	if (fd < 0) {
+		if (errno == ENOENT) {
+			printf("SKIP: /dev/vmclock_host not available\n");
+			return 4;
+		}
+		perror("open /dev/vmclock_host");
+		return 1;
+	}
+
+	clk = mmap(NULL, 4096, PROT_READ, MAP_SHARED, fd, 0);
+	if (clk == MAP_FAILED) {
+		perror("mmap");
+		close(fd);
+		return 1;
+	}
+
+	if (clk->magic != VMCLOCK_MAGIC) {
+		fprintf(stderr, "Bad magic: 0x%x\n", clk->magic);
+		ret = 1;
+		goto out;
+	}
+
+	if (clk->counter_id == VMCLOCK_COUNTER_INVALID) {
+		printf("SKIP: counter_id is INVALID (clocksource not TSC?)\n");
+		ret = 4;
+		goto out;
+	}
+
+	printf("vmclock_host: version=%u counter_id=%u time_type=%u status=%u\n",
+	       clk->version, clk->counter_id, clk->time_type, clk->clock_status);
+	printf("  tai_offset=%d\n", (int16_t)clk->tai_offset_sec);
+	printf("  counter_period_frac_sec=0x%" PRIx64 " shift=%u\n",
+	       (uint64_t)clk->counter_period_frac_sec, clk->counter_period_shift);
+
+	/* ABA comparison: read CLOCK_TAI, vmclock, CLOCK_TAI */
+	printf("\nABA comparison (vmclock vs CLOCK_TAI):\n");
+	for (i = 0; i < 10; i++) {
+		uint32_t seq;
+		int64_t tai_before, tai_after, vmclock_ns;
+		int64_t delta, window;
+
+		/* Read with seqcount retry */
+		do {
+			seq = clk->seq_count;
+			if (seq & 1) {
+				__asm__ volatile("pause" ::: "memory");
+				continue;
+			}
+			__asm__ volatile("" ::: "memory");
+
+			tai_before = clock_tai_ns();
+			uint64_t ctr = read_counter();
+			tai_after = clock_tai_ns();
+
+			__asm__ volatile("" ::: "memory");
+			if (clk->seq_count != seq)
+				continue;
+
+			vmclock_ns = vmclock_read_ns(clk, ctr);
+			break;
+		} while (1);
+
+		window = tai_after - tai_before;
+		/* vmclock should be between tai_before and tai_after */
+		delta = vmclock_ns - tai_before;
+
+		printf("  [%d] vmclock-tai_before=%+" PRId64 "ns window=%"
+		       PRId64 "ns", i, delta, window);
+
+		if (delta < -2000 || delta > window + 2000) {
+			printf(" FAIL (out of range)\n");
+			failures++;
+		} else {
+			printf(" OK\n");
+		}
+
+		usleep(100000); /* 100ms between samples */
+	}
+
+	if (failures) {
+		printf("\nFAIL: %d/%d samples out of range\n", failures, 10);
+		ret = 1;
+	} else {
+		printf("\nPASS: all samples within ABA window\n");
+	}
+
+out:
+	munmap((void *)clk, 4096);
+	close(fd);
+	return ret;
+}
-- 
2.54.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC PATCH v3 03/10] timekeeping: Account for monotonicity adjustment in ntp_error
  2026-05-20 13:33 ` [RFC PATCH v3 03/10] timekeeping: Account for monotonicity adjustment in ntp_error David Woodhouse
@ 2026-05-20 23:27   ` John Stultz
  2026-05-20 23:36     ` David Woodhouse
  0 siblings, 1 reply; 21+ messages in thread
From: John Stultz @ 2026-05-20 23:27 UTC (permalink / raw)
  To: David Woodhouse
  Cc: Richard Cochran, Wen Gu, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Thomas Gleixner,
	Stephen Boyd, Anna-Maria Behnsen, Frederic Weisbecker,
	Shuah Khan, Peter Zijlstra, Thomas Weißschuh, Arnd Bergmann,
	Miroslav Lichvar, Julien Ridoux, Ryan Luu, linux-kernel,
	David Woodhouse

On Wed, May 20, 2026 at 6:52 AM David Woodhouse <dwmw2@infradead.org> wrote:
>
> From: David Woodhouse <dwmw@amazon.co.uk>
>
> timekeeping_apply_adjustment() modifies xtime_nsec to maintain vDSO
> monotonicity when mult changes:
>
>     xtime_nsec -= offset
>
> This ensures that the time reported to userspace does not jump when the
> multiplier is adjusted from one tick to the next. However, the ntp_error
> accumulator which tracks the difference between intended and actual
> clock position was not being updated updated to reflect this additional
> discrepancy.
>
> An earlier attempt at this compensation existed as:
>
>     ntp_error -= (interval - offset) << ntp_error_shift
>
> but was removed in commit c2cda2a5bda9 ("timekeeping/ntp: Don't align
> NTP frequency adjustments to ticks") because it was a major source of
> NTP error. That's because (interval - offset) was wrong: the subtraction
> of "interval" prematurely accounted for the changed xtime_interval of
> the next tick, which would be correctly accounted in the next
> accumulation anyway — a double subtraction.
>
> What is actually needed is just the "offset" part: ntp_error must be
> told that xtime_nsec moved by "offset" without a corresponding change
> in the intended position. For the normal ±1 mult dithering this is
> negligible (the adjustments cancel over time), but for larger mult
> changes — such as when an external reference clock sets a new
> frequency — the one-time uncompensated offset is significant.
>
> Fix by adjusting ntp_error by the correct amount:
>
>     ntp_error += offset << ntp_error_shift
>
> This keeps ntp_error consistent with the actual xtime_nsec position
> after the adjustment.
>
> Fixes: c2cda2a5bda9 ("timekeeping/ntp: Don't align NTP frequency adjustments to ticks")
> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
> Assisted-by: Kiro:claude-opus-4.6-1m
> ---
>  kernel/time/timekeeping.c | 1 +
>  1 file changed, 1 insertion(+)
>
> diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
> index b84b05f9d460..95973e45d456 100644
> --- a/kernel/time/timekeeping.c
> +++ b/kernel/time/timekeeping.c
> @@ -2317,6 +2317,7 @@ 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;
>  }

Having the details in the comment above this block would have still
been nice, but..

Acked-by: John Stultz <jstultz@google.com>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC PATCH v3 01/10] MAINTAINERS: Add Miroslav as timekeeping reviewer
  2026-05-20 13:33 ` [RFC PATCH v3 01/10] MAINTAINERS: Add Miroslav as timekeeping reviewer David Woodhouse
@ 2026-05-20 23:28   ` John Stultz
  0 siblings, 0 replies; 21+ messages in thread
From: John Stultz @ 2026-05-20 23:28 UTC (permalink / raw)
  To: David Woodhouse
  Cc: Richard Cochran, Wen Gu, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Thomas Gleixner,
	Stephen Boyd, Anna-Maria Behnsen, Frederic Weisbecker,
	Shuah Khan, Peter Zijlstra, Thomas Weißschuh, Arnd Bergmann,
	Miroslav Lichvar, Julien Ridoux, Ryan Luu, linux-kernel,
	David Woodhouse

On Wed, May 20, 2026 at 6:52 AM David Woodhouse <dwmw2@infradead.org> wrote:
>
> From: David Woodhouse <dwmw@amazon.co.uk>
>
> If Thomas is going to nudge me on IRC to add Miroslav to Cc on
> timekeeping patches, then he might as well actually be listed in the
> MAINTAINERS file.
>
> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>

Thanks for putting this one together!

Acked-by: John Stultz <jstultz@google.com>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC PATCH v3 04/10] timekeeping: Guard against divide-by-zero in timekeeping_adjust
  2026-05-20 13:33 ` [RFC PATCH v3 04/10] timekeeping: Guard against divide-by-zero in timekeeping_adjust David Woodhouse
@ 2026-05-20 23:29   ` John Stultz
  0 siblings, 0 replies; 21+ messages in thread
From: John Stultz @ 2026-05-20 23:29 UTC (permalink / raw)
  To: David Woodhouse
  Cc: Richard Cochran, Wen Gu, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Thomas Gleixner,
	Stephen Boyd, Anna-Maria Behnsen, Frederic Weisbecker,
	Shuah Khan, Peter Zijlstra, Thomas Weißschuh, Arnd Bergmann,
	Miroslav Lichvar, Julien Ridoux, Ryan Luu, linux-kernel,
	David Woodhouse

On Wed, May 20, 2026 at 6:52 AM David Woodhouse <dwmw2@infradead.org> wrote:
>
> From: David Woodhouse <dwmw@amazon.co.uk>
>
> When the TSC clocksource is recalibrated (e.g. on KVM guests with
> clocksource=tsc), cycle_interval can momentarily be zero during the
> transition. Guard the div64_u64 in timekeeping_adjust() to prevent a
> divide-by-zero oops.
>
> This can be triggered on KVM guests that force clocksource=tsc when
> the guest's measured TSC frequency doesn't match what KVM reported,
> causing a recalibration during boot.
>
> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
> Assisted-by: Kiro:claude-opus-4.6-1m

Acked-by: John Stultz <jstultz@google.com>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC PATCH v3 03/10] timekeeping: Account for monotonicity adjustment in ntp_error
  2026-05-20 23:27   ` John Stultz
@ 2026-05-20 23:36     ` David Woodhouse
  0 siblings, 0 replies; 21+ messages in thread
From: David Woodhouse @ 2026-05-20 23:36 UTC (permalink / raw)
  To: John Stultz
  Cc: Richard Cochran, Wen Gu, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Thomas Gleixner,
	Stephen Boyd, Anna-Maria Behnsen, Frederic Weisbecker,
	Shuah Khan, Peter Zijlstra, Thomas Weißschuh, Arnd Bergmann,
	Miroslav Lichvar, Julien Ridoux, Ryan Luu, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1235 bytes --]

On Wed, 2026-05-20 at 16:27 -0700, John Stultz wrote:
> 
> > --- a/kernel/time/timekeeping.c
> > +++ b/kernel/time/timekeeping.c
> > @@ -2317,6 +2317,7 @@ 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;
> >   }
> 
> Having the details in the comment above this block would have still
> been nice, but..
> 

I couldn't get past the fact that it ought to be *obvious* that if we
adjust xtime_nsec, we have to adjust ntp_error accordingly. Having it
in the very next line should be comment enough.

But I guess I could add something to the comment block, saying
something along the lines of 

 * When subtracting offset from xtime_nsec, the same offset has to
 * be added to ntp_error, which accumulates the delta between the
 * time reported in xtime_nsec, and the intended time.

In retrospect, I realise that's probably what you were asking me for
before, but I was being dim. Apologies.

> Acked-by: John Stultz <jstultz@google.com>

Thanks.

[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC PATCH v3 10/10] kernel/time: Add /dev/vmclock_host miscdev
  2026-05-20 13:33 ` [RFC PATCH v3 10/10] kernel/time: Add /dev/vmclock_host miscdev David Woodhouse
@ 2026-05-21  6:29   ` Wen Gu
  2026-05-28 16:18     ` David Woodhouse
  0 siblings, 1 reply; 21+ messages in thread
From: Wen Gu @ 2026-05-21  6:29 UTC (permalink / raw)
  To: David Woodhouse, Richard Cochran, 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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel
  Cc: David Woodhouse



On 2026/5/20 21:33, David Woodhouse wrote:

> +/*
> + * Called from pvclock_gtod_notify on every timekeeping update.
> + * Only does real work when ntp_tick or skew_delta changes.
> + */
> +static int vmclock_host_notify(struct notifier_block *nb,
> +			       unsigned long was_set, void *data)
> +{

<...>

> +
> +	/* Compute time tuple: C = A + ntp_error + time_offset */
> +	ns = tk->tkr_mono.xtime_nsec >> tk->tkr_mono.shift;
> +	sec = tk->xtime_sec + tk->tai_offset;
> +	ns += tk->ntp_error >> (tk->tkr_mono.shift + tk->ntp_error_shift);
> +	ns += ntp_get_time_offset_ns(tk->id);
> +
> +	while (ns < 0) {
> +		ns += NSEC_PER_SEC;
> +		sec--;
> +	}
> +	while (ns >= NSEC_PER_SEC) {
> +		ns -= NSEC_PER_SEC;
> +		sec++;
> +	}
> +
> +	counter_value = tk->tkr_mono.cycle_last;
> +	hi = div64_u64_rem((u64)ns << 32, 1000000000ULL, &rem);
> +	time_frac = (hi << 32) | div64_u64(rem << 32, 1000000000ULL);
> +
> +	clock_status = !(ntp_get_status() & STA_UNSYNC) ?
> +		VMCLOCK_STATUS_SYNCHRONIZED : VMCLOCK_STATUS_FREERUNNING;
> +
> +	/* Prepare le values */
> +	le_counter_value = cpu_to_le64(counter_value);
> +	le_time_sec = cpu_to_le64(sec);
> +	le_time_frac = cpu_to_le64(time_frac);
> +	le_period_frac = cpu_to_le64(cached_period_frac);
> +	period_shift = cached_period_shift;
> +
> +	/* Update page under seqcount */
> +	WRITE_ONCE(clk->seq_count, cpu_to_le32(
> +		le32_to_cpu(READ_ONCE(clk->seq_count)) + 1));
> +	smp_wmb();
> +
> +	clk->counter_id = counter_id;
> +	clk->counter_value = le_counter_value;
> +	clk->time_sec = le_time_sec;
> +	clk->time_frac_sec = le_time_frac;
> +	if (period_changed) {
> +		clk->counter_period_frac_sec = le_period_frac;
> +		clk->counter_period_shift = period_shift;
> +	}
> +	clk->clock_status = clock_status;
> +
> +	/* Set leap second indicator from NTP time_state */
> +	switch (ntp_get_time_state()) {
> +	case TIME_INS:
> +		clk->leap_indicator = VMCLOCK_LEAP_PRE_POS;
> +		break;
> +	case TIME_DEL:
> +		clk->leap_indicator = VMCLOCK_LEAP_PRE_NEG;
> +		break;
> +	case TIME_OOP:
> +		clk->leap_indicator = VMCLOCK_LEAP_POS;
> +		break;
> +	case TIME_WAIT:
> +		clk->leap_indicator = (ntp_get_status() & STA_DEL) ?
> +			VMCLOCK_LEAP_POST_NEG : VMCLOCK_LEAP_POST_POS;
> +		break;
> +	default:
> +		clk->leap_indicator = VMCLOCK_LEAP_NONE;
> +		break;
> +	}
> +
> +	/* Export as TAI if tai_offset is known, otherwise UTC */
> +	if (tk->tai_offset) {
> +		clk->time_type = VMCLOCK_TIME_TAI;
> +		clk->tai_offset_sec = cpu_to_le16((s16)tk->tai_offset);

I think this should be cpu_to_le16(-(s16)tk->tai_offset)?

vmclock_set_tk_reference() in Patch 9 treats clk->tai_offset_sec as
a (UTC - TAI) offset, so the sign here probably needs to be inverted.

> +		clk->flags = cpu_to_le64(VMCLOCK_FLAG_TAI_OFFSET_VALID |
> +					 VMCLOCK_FLAG_TIME_MONOTONIC |
> +					 VMCLOCK_FLAG_NOTIFICATION_PRESENT);
> +	} else {
> +		clk->time_type = VMCLOCK_TIME_UTC;
> +		clk->tai_offset_sec = 0;
> +		clk->flags = cpu_to_le64(VMCLOCK_FLAG_TIME_MONOTONIC |
> +					 VMCLOCK_FLAG_NOTIFICATION_PRESENT);
> +	}
> +
> +	smp_wmb();
> +	WRITE_ONCE(clk->seq_count, cpu_to_le32(
> +		le32_to_cpu(READ_ONCE(clk->seq_count)) + 1));
> +
> +	wake_up_interruptible(&vmclock_wait);
> +	return NOTIFY_DONE;
> +}

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC PATCH v3 02/10] timekeeping: Remove xtime_remainder from ntp_error accumulation
  2026-05-20 13:33 ` [RFC PATCH v3 02/10] timekeeping: Remove xtime_remainder from ntp_error accumulation David Woodhouse
@ 2026-05-22 11:41   ` David Woodhouse
  2026-05-29 23:21     ` John Stultz
  0 siblings, 1 reply; 21+ messages in thread
From: David Woodhouse @ 2026-05-22 11:41 UTC (permalink / raw)
  To: Richard Cochran, Wen Gu, 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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 5016 bytes --]

On Wed, 2026-05-20 at 14:33 +0100, David Woodhouse wrote:
> From: David Woodhouse <dwmw@amazon.co.uk>
> 
> The ntp_error accumulator tracks the difference between the time actually
> reported to consumers in xtime, and the *intended* time. The former is
> subject to a sawtooth effect due to the quantisation of 'mult', which
> means that it actually advances by 'xtime_interval' each tick, while
> the intended clock advances by 'ntp_tick'.
> 
> By dithering between adjacent integer values of 'mult' which result in
> an 'xtime_interval' slightly higher/lower than the intended tick length,
> the advancement of xtime is kept on average to the intended rate.
> 
> The accounting should therefore adjust ntp_error by adding ntp_tick and
> subtracting xtime_interval on each tick.
> 
> Since commit a386b5af8edd ("time: Compensate for rounding on
> odd-frequency clocksources") the value subtracted has been
> (xtime_interval + xtime_remainder), which is wrong. The effect is a
> systematic drift whose magnitude depends on the value of xtime_remainder
> and the NTP frequency correction. NTP masks this by continuously
> adjusting the frequency to compensate, but with a fixed frequency (or an
> external reference clock like vmclock), the drift is exposed.
> 
> The value of xtime_remainder actually does represent the difference
> between the tick period and xtime_interval, so simply adding it instead
> of (+ tick length - xtime_remainder) might have made sense... except
> that it's only calculated once at boot time, so it's inaccurate anyway.
> So just kill it with fire.
> 
> Also remove it from the mult computation in timekeeping_adjust(), which
> used it to offset the division for the same (incorrect) reason.
> 
> Fixes: a386b5af8edd ("time: Compensate for rounding on odd-frequency clocksources")
> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
> Assisted-by: Kiro:claude-opus-4.6-1m

Hi John, I note you skipped this one and acked some of the rest of the
series. Do you have concerns?

This is the key change:

> @@ -2463,8 +2462,7 @@ static u64 logarithmic_accumulation(struct timekeeper *tk, u64 offset,
>  
>  	/* Accumulate error between NTP and clock interval */
>  	tk->ntp_error += tk->ntp_tick << shift;
> -	tk->ntp_error -= (tk->xtime_interval + tk->xtime_remainder) <<
> -						(tk->ntp_error_shift + shift);
> +	tk->ntp_error -= tk->xtime_interval << (tk->ntp_error_shift + shift);
>  
>  	return offset;
>  }

We discussed this a bit, and as far as I could tell we were in violent
agreement.

The NTP code wants the time to advance by tk->ntp_tick in each tick,
but it actually only advances by tk->xtime_interval.

They differ due to the quantization error; tk->xtime_interval is the
precise multiple of the value selected for 'mult' for the tick that is
being accounted.

Therefore, ntp_error needs to accumulate the difference between the
two. Which used to be called 'xtime_remainder'.

By adding ntp_tick and subtracting xtime_interval, the code above *is*
accumulating precisely that difference.

It doesn't need to *also* subtract the value of 'xtime_remainder'
again, as it did before. That would actually render the whole
adjustment a no-op. Ignoring units, for brevity:

Given that¹:
  ntp_tick = xtime_interval + xtime_remainder

These are therefore equivalent:
  ntp_error += xtime_remainder
  ntp_error += ntp_tick - xtime_interval

And *this*, as we currently have in our tree, is a no-op:
  ntp_error += ntp_tick - (xtime_interval + xtime_remainder)

¹ well... it *would* be precisely a no-op if xtime_remainder was
actually kept accurate and it *was* the delta between ntp_tick and
xtime_interval at any given moment. In fact it's calculated once at
boot and never updated, so the net effect of the whole adjustment which
is being modified in the patch hunk above... is to repeatedly add a
tiny bit of error *instead* of the intended adjustment?

I'm reading back your messages and trying to work out if you were
trying to tell me that this stale boot time value of xtime_remainder
is somehow actually doing something sane... but I don't believe so?

I've debugged this by adding *absolute* tracking of the 'intended'
times which I called

 • B: xtime+ntp_error (where NTP wants us to be right now)
 • C: xtime_ntp_error+time_offset (where NTP wants to skew to *eventually*)

With the fixes in this series, the per-tick sanity checks that
ntp_error==B-xtime, and time_offset==C-B are all passing. I'm
confident, both based on code analysis and empirical testing, that the
changes here are correct.

I can now invoke the 'timekeeping_set_reference' proof of concept which
sets tick_length and ntp_error+time_offset to ask the kernel to follow
a specific line, and watch the core timekeeping *clamp* to it and
follow it with basically 0ns jitter; occasionally showing 1ns as the
inter-tick jitter due to 'mult' dithering shows up.









[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock
  2026-05-20 13:33 [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
                   ` (9 preceding siblings ...)
  2026-05-20 13:33 ` [RFC PATCH v3 10/10] kernel/time: Add /dev/vmclock_host miscdev David Woodhouse
@ 2026-05-23 11:49 ` David Woodhouse
  10 siblings, 0 replies; 21+ messages in thread
From: David Woodhouse @ 2026-05-23 11:49 UTC (permalink / raw)
  To: Richard Cochran, Wen Gu, 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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1797 bytes --]

On Wed, 2026-05-20 at 14:33 +0100, David Woodhouse wrote:
> This is v3 of the series to allow feed-forward clock discipline, allowing
> a guest kernel to lock its system clock directly to a hypervisor-provided
> vmclock reference with nanosecond precision and no drift.
> 
> With all the drift-inducing bugs in the core timekeeping resolved in the 
> first patches of the series, the RFC timekeeping_set_reference() 
> function basically just sets the tick length and time_offset according 
> to the reference, and lets the now-fixed core timekeeping get on with
> its job.

I wanted to see what effect that had, if any, on normal NTP timekeeping
with chrony.

I set four identical bare metal hosts running the baseline 7.1-rc4+
kernel. With and without my timekeeping fixes, a pair with NO_HZ_IDLE
and a pair with HZ_PERIODIC (at HZ=1000).

I'll let the test run over the weekend; collating the data is a semi-
manual set of script hacks but for the time being this is being kept
updated with the latest results: https://david.woodhou.se/ntptest/

Any feedback on the analysis — and especially on the raw data that I'm
collecting — would be welcome.

This is just using chrony and NTP, no PHC as I wanted to simulate a
"normal" consumer-style setup.

In a future test I'll set up a proper feed-forward discipline and use
timekeeping_set_reference() to steer the kernel's timekeeping based
directly on the TSC.

I suspect that will give better results especially in the nohz case,
because chrony can only discipline what it *sees*, and has no way to
see the "intended" values including ntp_error and time_offset; only
what the actual xtime output has sawtoothed to after prolonged and
unpredictable periods of no ticks to drive the 'mult' dithering.




[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC PATCH v3 10/10] kernel/time: Add /dev/vmclock_host miscdev
  2026-05-21  6:29   ` Wen Gu
@ 2026-05-28 16:18     ` David Woodhouse
  0 siblings, 0 replies; 21+ messages in thread
From: David Woodhouse @ 2026-05-28 16:18 UTC (permalink / raw)
  To: Wen Gu, Richard Cochran, 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,
	Thomas Weißschuh, Arnd Bergmann, Miroslav Lichvar,
	Julien Ridoux, Ryan Luu, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 614 bytes --]

On Thu, 2026-05-21 at 14:29 +0800, Wen Gu wrote:
> 
> I think this should be cpu_to_le16(-(s16)tk->tai_offset)?
> 
> vmclock_set_tk_reference() in Patch 9 treats clk->tai_offset_sec as
> a (UTC - TAI) offset, so the sign here probably needs to be inverted.

Yes, thank you. Arguably this is a "bug" in the vmclock specification,
which defines it the wrong way round to normal people. But it's baked
into the vmclock guest driver which has been in the kernel for a while
now, so I guess it's too late to fix it, and we just have to document
it clearly. 

It's not *wrong* per se; just unconventional.

[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC PATCH v3 02/10] timekeeping: Remove xtime_remainder from ntp_error accumulation
  2026-05-22 11:41   ` David Woodhouse
@ 2026-05-29 23:21     ` John Stultz
  2026-05-30 10:51       ` David Woodhouse
  0 siblings, 1 reply; 21+ messages in thread
From: John Stultz @ 2026-05-29 23:21 UTC (permalink / raw)
  To: David Woodhouse
  Cc: Richard Cochran, Wen Gu, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Thomas Gleixner,
	Stephen Boyd, Anna-Maria Behnsen, Frederic Weisbecker,
	Shuah Khan, Peter Zijlstra, Thomas Weißschuh, Arnd Bergmann,
	Miroslav Lichvar, Julien Ridoux, Ryan Luu, linux-kernel

On Fri, May 22, 2026 at 4:41 AM David Woodhouse <dwmw2@infradead.org> wrote:
>
> On Wed, 2026-05-20 at 14:33 +0100, David Woodhouse wrote:
> > From: David Woodhouse <dwmw@amazon.co.uk>
> >
> > The ntp_error accumulator tracks the difference between the time actually
> > reported to consumers in xtime, and the *intended* time. The former is
> > subject to a sawtooth effect due to the quantisation of 'mult', which
> > means that it actually advances by 'xtime_interval' each tick, while
> > the intended clock advances by 'ntp_tick'.
> >
> > By dithering between adjacent integer values of 'mult' which result in
> > an 'xtime_interval' slightly higher/lower than the intended tick length,
> > the advancement of xtime is kept on average to the intended rate.
> >
> > The accounting should therefore adjust ntp_error by adding ntp_tick and
> > subtracting xtime_interval on each tick.
> >
> > Since commit a386b5af8edd ("time: Compensate for rounding on
> > odd-frequency clocksources") the value subtracted has been
> > (xtime_interval + xtime_remainder), which is wrong. The effect is a
> > systematic drift whose magnitude depends on the value of xtime_remainder
> > and the NTP frequency correction. NTP masks this by continuously
> > adjusting the frequency to compensate, but with a fixed frequency (or an
> > external reference clock like vmclock), the drift is exposed.
> >
> > The value of xtime_remainder actually does represent the difference
> > between the tick period and xtime_interval, so simply adding it instead
> > of (+ tick length - xtime_remainder) might have made sense... except
> > that it's only calculated once at boot time, so it's inaccurate anyway.
> > So just kill it with fire.
> >
> > Also remove it from the mult computation in timekeeping_adjust(), which
> > used it to offset the division for the same (incorrect) reason.
> >
> > Fixes: a386b5af8edd ("time: Compensate for rounding on odd-frequency clocksources")
> > Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
> > Assisted-by: Kiro:claude-opus-4.6-1m
>
> Hi John, I note you skipped this one and acked some of the rest of the
> series. Do you have concerns?
>

Sorry, I've been busy with other things and haven't had much time to
sit and think through the details on this one.

You've pointed out well that the error in calculating the mult value
(basically what gets rounded off) does get handled in the ntp_err and
the resulting mult adjustments.

My concern has mostly been for coarse grained clocksources, the error
in the computed cycle_interval may not line up well with the desired
ntp_interval and could be a secondary cause of error.

Now, I think part of our disconnect has been my working mental model
apparently pre-dates 78b98e3c5a66 ("timekeeping/ntp: Determine the
multiplier directly from NTP tick length"), where while the mult/shift
was calculated from the provided frequency, then we calculated the
cycle_interval to best match the ntp_interval in the same way as the
current code. We did the mult adjustment was done in proprortion to
the size of the ntp_error. This meant the quantization error for the
mult value was separate from the quantization error for the calculated
cycle_interval.

But I think after 78b98e3c5a66, since we recalculate the mult from the
ntp_tick/cycle_interval in timekeeping_adjust(), it means the
quantization error in the cycle_interval is reflected in the mult, so
the mult adjustment ("dithering") effectively cancels it out.

So I think that is why I was mistakenly concerned about this change.

Now, I'm still a little guarded as while I know you've done thorough
testing with the fine grained TSC, it's not clear if you've done
similar analysis with other clocksources (ACPI PM I think was the one
that motviated the change you are undoing). So that might strengthen
the argument.  But looking more carefully at current code, I think
you've got a good case.

Thanks again for putting up with my foggy memory and handwringing.  :)

Acked-by: John Stultz <jstultz@google.com>

thanks
-john

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC PATCH v3 02/10] timekeeping: Remove xtime_remainder from ntp_error accumulation
  2026-05-29 23:21     ` John Stultz
@ 2026-05-30 10:51       ` David Woodhouse
  0 siblings, 0 replies; 21+ messages in thread
From: David Woodhouse @ 2026-05-30 10:51 UTC (permalink / raw)
  To: John Stultz
  Cc: Richard Cochran, Wen Gu, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Thomas Gleixner,
	Stephen Boyd, Anna-Maria Behnsen, Frederic Weisbecker,
	Shuah Khan, Peter Zijlstra, Thomas Weißschuh, Arnd Bergmann,
	Miroslav Lichvar, Julien Ridoux, Ryan Luu, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 5073 bytes --]

On Fri, 2026-05-29 at 16:21 -0700, John Stultz wrote:
> On Fri, May 22, 2026 at 4:41 AM David Woodhouse <dwmw2@infradead.org> wrote:
> > 
> > On Wed, 2026-05-20 at 14:33 +0100, David Woodhouse wrote:
> > > From: David Woodhouse <dwmw@amazon.co.uk>
> > > 
> > > The ntp_error accumulator tracks the difference between the time actually
> > > reported to consumers in xtime, and the *intended* time. The former is
> > > subject to a sawtooth effect due to the quantisation of 'mult', which
> > > means that it actually advances by 'xtime_interval' each tick, while
> > > the intended clock advances by 'ntp_tick'.
> > > 
> > > By dithering between adjacent integer values of 'mult' which result in
> > > an 'xtime_interval' slightly higher/lower than the intended tick length,
> > > the advancement of xtime is kept on average to the intended rate.
> > > 
> > > The accounting should therefore adjust ntp_error by adding ntp_tick and
> > > subtracting xtime_interval on each tick.
> > > 
> > > Since commit a386b5af8edd ("time: Compensate for rounding on
> > > odd-frequency clocksources") the value subtracted has been
> > > (xtime_interval + xtime_remainder), which is wrong. The effect is a
> > > systematic drift whose magnitude depends on the value of xtime_remainder
> > > and the NTP frequency correction. NTP masks this by continuously
> > > adjusting the frequency to compensate, but with a fixed frequency (or an
> > > external reference clock like vmclock), the drift is exposed.
> > > 
> > > The value of xtime_remainder actually does represent the difference
> > > between the tick period and xtime_interval, so simply adding it instead
> > > of (+ tick length - xtime_remainder) might have made sense... except
> > > that it's only calculated once at boot time, so it's inaccurate anyway.
> > > So just kill it with fire.
> > > 
> > > Also remove it from the mult computation in timekeeping_adjust(), which
> > > used it to offset the division for the same (incorrect) reason.
> > > 
> > > Fixes: a386b5af8edd ("time: Compensate for rounding on odd-frequency clocksources")
> > > Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
> > > Assisted-by: Kiro:claude-opus-4.6-1m
> > 
> > Hi John, I note you skipped this one and acked some of the rest of the
> > series. Do you have concerns?
> > 
> 
> Sorry, I've been busy with other things and haven't had much time to
> sit and think through the details on this one.
> 
> You've pointed out well that the error in calculating the mult value
> (basically what gets rounded off) does get handled in the ntp_err and
> the resulting mult adjustments.
> 
> My concern has mostly been for coarse grained clocksources, the error
> in the computed cycle_interval may not line up well with the desired
> ntp_interval and could be a secondary cause of error.

I'm tracking nanosecond precision. They're *all* coarse-grained to me :)

But you may be right. Not 'secondary cause of error' per se, but
perhaps not quite desirable behaviour...

The issue with coarser-grained clocksources is that the closest integer
multiple (mult) of cycle_interval is further from the intended tick
length.

For ACPI PM timer at 3.579545 MHz and CONFIG_HZ=1000 we end up with a
cycle_interval of 3579545 / 1000 = 3579.545 which tk_setup_internals()
rounds up to 3580.

With a perfect oscillator, that means each tick (3580 counts) actually
takes 1000124.7 ns (+125PPM vs. the theoretical), and we set 'mult' for
CLOCK_MONOTONIC_RAW accordingly (and for CLOCK_MONOTONIC too before NTP
kicks in).

So *that* is why we add 'ntp_tick + xtime_remainder' to one side of the
balance (while subtracting the actual xtime_interval). It's because
that basically *is* part of the tick length that we know 3580 counts to
be.

It's not entirely clear from the variable name or the commit message
that this was truly understood :)

Since NTP only works on ratios, I was right that it's a constant factor
that NTP can skew away, but that +125PPM is actually a significant
proportion of the maximum ±500PPM skew that adjtimex() will permit, so
there's only another +375PPM tolerance for the oscillator to actually
vary.

I switched https://david.woodhou.se/ntptest-virt/ to acpi_pm and you
can see it clearly in the frequency correction graph: my patched hosts
are showing precisely that -125PPM correction because of the tick
length.

So xtime_remainder is basically a bias to bring the behaviour of a
perfect oscillator right into the middle of the ±500PPM correction
band.

I will look at renaming it, or folding it into tick_length since that's
*actually* what it is (perhaps tk_setup_internals() should be setting
ntpinterval to cycle_interval * mult as a much cleaner way of achieving
the same thing, although ntp_update_frequency() would need to cope?)

At the very least, I can document it better. And perhaps I can make the
absolute reference tracking take it into account as part of
tick_length.

Thanks for nudging me to look closer.


[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2026-05-30 10:54 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-20 13:33 [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock David Woodhouse
2026-05-20 13:33 ` [RFC PATCH v3 01/10] MAINTAINERS: Add Miroslav as timekeeping reviewer David Woodhouse
2026-05-20 23:28   ` John Stultz
2026-05-20 13:33 ` [RFC PATCH v3 02/10] timekeeping: Remove xtime_remainder from ntp_error accumulation David Woodhouse
2026-05-22 11:41   ` David Woodhouse
2026-05-29 23:21     ` John Stultz
2026-05-30 10:51       ` David Woodhouse
2026-05-20 13:33 ` [RFC PATCH v3 03/10] timekeeping: Account for monotonicity adjustment in ntp_error David Woodhouse
2026-05-20 23:27   ` John Stultz
2026-05-20 23:36     ` David Woodhouse
2026-05-20 13:33 ` [RFC PATCH v3 04/10] timekeeping: Guard against divide-by-zero in timekeeping_adjust David Woodhouse
2026-05-20 23:29   ` John Stultz
2026-05-20 13:33 ` [RFC PATCH v3 05/10] timekeeping: Drive time_offset skew via per-tick ntp_error transfer David Woodhouse
2026-05-20 13:33 ` [RFC PATCH v3 06/10] ntp: Convert adjtime() to use time_offset instead of tick_length inflation David Woodhouse
2026-05-20 13:33 ` [RFC PATCH v3 07/10] ntp: Remove tick_length_base, use tick_length directly David Woodhouse
2026-05-20 13:33 ` [RFC PATCH v3 08/10] timekeeping: Add absolute reference for feed-forward clock discipline David Woodhouse
2026-05-20 13:33 ` [RFC PATCH v3 09/10] ptp_vmclock: Feed reference to timekeeping for feed-forward discipline David Woodhouse
2026-05-20 13:33 ` [RFC PATCH v3 10/10] kernel/time: Add /dev/vmclock_host miscdev David Woodhouse
2026-05-21  6:29   ` Wen Gu
2026-05-28 16:18     ` David Woodhouse
2026-05-23 11:49 ` [RFC PATCH v3 00/10] timekeeping: Fix drift tracking precision and add feed-forward discipline via vmclock 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®