From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from galois.linutronix.de (Galois.linutronix.de [193.142.43.55]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D8A0D3B42CD; Fri, 10 Jul 2026 07:25:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=193.142.43.55 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783668359; cv=none; b=VHMXHSDE6UbXFZxtIaZ1kIvODwWgE14aT46S0Et+SCII1sprAP5r1mJ0lFH1opwwQaZ5yX7KjKtIz5oIbAl5gPqU3qBdLiiQSTAmxNpJDiQn5gU3XHtEVbY6nUP/WPwSqnRaEU9ojO94IEM4ESDVejKaAuv0rMn6SxwLBFM2XvA= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783668359; c=relaxed/simple; bh=A9qAgY+02nLzb1fycZVJmCPLaDnGSokUXnmz7GFSI6s=; h=Date:From:To:Subject:Cc:In-Reply-To:References:MIME-Version: Message-ID:Content-Type; b=SM1rlsjkbPypN0LlfiBYQaKYO0GEAARcq5VbW0BjMsZp5IrpmduFdOXZw4OZ1HO3sQpB83bFmPexgwgipy6BZmlLpyf+VUkLYjZ4Jy0t/zZwr6V2gmWWF8rUp/dBYzsr4dUcYBVldihsb27tz3Pg0vX+JoNoSejhoJyAjM2tSOw= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linutronix.de; spf=pass smtp.mailfrom=linutronix.de; dkim=pass (2048-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=phJSs+Wh; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=EkmYDCAU; arc=none smtp.client-ip=193.142.43.55 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linutronix.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="phJSs+Wh"; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="EkmYDCAU" Date: Fri, 10 Jul 2026 07:25:52 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020; t=1783668354; h=from:from:sender:sender:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=2Cax8wqv6uO5R0YEF2UIno75z3p9m7cAJlm+dGMvzJQ=; b=phJSs+WhpCI3MRhh69/m6YbpkrytPnHSvIISXKB+3odDk7qQEyAv3TT1HY5/RRmSJvPnp6 gjcGEvwgvbkoiv3od7sbemwafeWzWrkgHmkFpEE2EiAeRWHmmLlX/HTYeAxoPFXjUIkhZI 1g9LG/5+wr4GAkvVV6XE0ClhgoZ7bBg4uxvppEAw9XRQXo7MZyZ3L6GffICEIAfx9Xkude GQfMPQDIe2VgiiFsN8Sy5cmBFZ+nHU3c2vdv2UcPJOKIZ0t1yl6vx8tjMv/VDdf+ajVJ1J rouAZ5a+1adQCrxeZUug0wj2xBCKCpJnKtaZE5SkBVYqkQewfoa2wsgRtzqt3Q== DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020e; t=1783668354; h=from:from:sender:sender:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=2Cax8wqv6uO5R0YEF2UIno75z3p9m7cAJlm+dGMvzJQ=; b=EkmYDCAUjmBbZ1AQZ1Awp/ANJuIwQXIdIYeT23OfGj86QzLkD24zkpAzv+D0lY2lbxJNCQ wnxpqyHWYLk6aLCg== From: "tip-bot2 for David Woodhouse" Sender: tip-bot2@linutronix.de Reply-to: linux-kernel@vger.kernel.org To: linux-tip-commits@vger.kernel.org Subject: [tip: timers/core] timekeeping: Drive time_adjust skew via per-tick ntp_error transfer Cc: David Woodhouse , Thomas Gleixner , x86@kernel.org, linux-kernel@vger.kernel.org In-Reply-To: <20260621220051.1030462-6-dwmw2@infradead.org> References: <20260621220051.1030462-6-dwmw2@infradead.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-ID: <178366835267.744054.14616552717097046637.tip-bot2@tip-bot2> Robot-ID: Robot-Unsubscribe: Contact to get blacklisted from these emails Precedence: bulk Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The following commit has been merged into the timers/core branch of tip: Commit-ID: 289d1759494f4a97b0ca4565f3eed00964b952c4 Gitweb: https://git.kernel.org/tip/289d1759494f4a97b0ca4565f3eed00964b= 952c4 Author: David Woodhouse AuthorDate: Sun, 21 Jun 2026 22:53:58 +01:00 Committer: Thomas Gleixner CommitterDate: Fri, 10 Jul 2026 09:20:54 +02:00 timekeeping: Drive time_adjust skew via per-tick ntp_error transfer The legacy adjtime() slew (ADJ_OFFSET_SINGLESHOT) was the last user of tick_length !=3D tick_length_base: it slewed the clock by inflating tick_length directly, which delivered the correction imprecisely (e.g. delivering only 4997.5=C2=B5s when asked for a 5ms skew). Deliver it accurately through the same per-tick mechanism that is now used for time_offset, allowing it to contribute to skew_delta and thus drive the delivery through ntp_error and mult selection. To allow for accurate accounting, store the sub-microsecond part of time_adjust is separately, while keeping time_adjust in microseconds as that's the external API. Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Assisted-by: Kiro:claude-opus-4.8 Link: https://patch.msgid.link/20260621220051.1030462-6-dwmw2@infradead.org --- kernel/time/ntp.c | 136 ++++++++++++++++++++++++++++--------- kernel/time/timekeeping.c | 2 +- 2 files changed, 107 insertions(+), 31 deletions(-) diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c index 064e68e..20d211b 100644 --- a/kernel/time/ntp.c +++ b/kernel/time/ntp.c @@ -41,6 +41,8 @@ * @time_freq: Frequency offset scaled nsecs/secs * @time_reftime: Time at last adjustment in seconds * @time_adjust: Adjustment value + * @time_adjust_frac: Sub-microsecond remainder of @time_adjust being + * delivered, in ns << NTP_SCALE_SHIFT (not divided by HZ). * @ntp_tick_adj: Constant boot-param configurable NTP tick adjustment (upsc= aled) * @cs_tick_adj: Fixed per-second adjustment compensating for the difference * between the nominal NTP interval and the real time taken @@ -77,6 +79,7 @@ struct ntp_data { s64 time_freq; time64_t time_reftime; long time_adjust; + s64 time_adjust_frac; s64 ntp_tick_adj; s64 cs_tick_adj; time64_t ntp_next_leap_sec; @@ -110,6 +113,9 @@ static struct ntp_data tk_ntp_data[TIMEKEEPERS_MAX] =3D { =20 #define SECS_PER_DAY 86400 #define MAX_TICKADJ 500LL /* usecs */ +/* One microsecond of phase, in plain shifted-ns (ns << NTP_SCALE_SHIFT) */ +#define ONE_US_NS ((s64)NSEC_PER_USEC << NTP_SCALE_SHIFT) +/* Per-tick MAX_TICKADJ slew, in plain shifted-ns */ #define MAX_TICKADJ_SCALED \ (((MAX_TICKADJ * NSEC_PER_USEC) << NTP_SCALE_SHIFT) / NTP_INTERVAL_FREQ) #define MAX_TAI_OFFSET 100000 @@ -345,6 +351,7 @@ static void __ntp_clear(struct ntp_data *ntpdata) { /* Stop active adjtime() */ ntpdata->time_adjust =3D 0; + ntpdata->time_adjust_frac =3D 0; ntpdata->time_status |=3D STA_UNSYNC; ntpdata->time_maxerror =3D NTP_PHASE_LIMIT; ntpdata->time_esterror =3D NTP_PHASE_LIMIT; @@ -422,19 +429,87 @@ static s64 ntp_drain_time_offset(unsigned int tkid, s64= amount) } =20 /* + * Drain the legacy adjtime() correction (time_adjust) as it is delivered. + * + * @amount is the total intentional per-tick skew for this accumulation + * (skew_delta << shift), in time_offset units (shifted_ns / HZ); it covers + * both the exponential time_offset slew and the linear adjtime slew. This + * function claims only the adjtime share =E2=80=94 capped at the MAX_TICKAD= J rate =E2=80=94 + * and returns the remainder for ntp_drain_time_offset(). + * + * time_adjust is in whole =C2=B5s. The sub-=C2=B5s remainder being delivere= d lives in + * time_adjust_frac (plain shifted-ns, i.e. ns << NTP_SCALE_SHIFT -- unlike + * time_offset these are NOT pre-divided by HZ); we top it up by borrowing + * whole microseconds from time_adjust as the drain consumes it. + */ +static s64 ntp_drain_time_adjust(unsigned int tkid, s64 amount, unsigned int= shift) +{ + struct ntp_data *ntpdata =3D &tk_ntp_data[tkid]; + /* Sign reference: time_adjust if any whole us remain, else the drawer */ + s64 ref =3D ntpdata->time_adjust ? (s64)ntpdata->time_adjust + : ntpdata->time_adjust_frac; + s64 deliver, deficit, claimed; + + if (!amount || !ref || signof(amount) !=3D signof(ref)) + return amount; + + /* + * Phase to deliver this accumulation, in plain shifted-ns. The drain + * @amount is in =C3=B7HZ units, so multiply by HZ first, then clamp to the + * MAX_TICKADJ rate (MAX_TICKADJ_SCALED is the per-tick slew in + * shifted-ns). Multiply-then-clamp avoids an s64 divide for the cap. + */ + deliver =3D min(abs(amount) * NTP_INTERVAL_FREQ, + (s64)MAX_TICKADJ_SCALED << shift); + + /* Top up the sub-=C2=B5s drawer from whole-=C2=B5s time_adjust as needed */ + deficit =3D deliver - abs(ntpdata->time_adjust_frac); + if (deficit > 0 && ntpdata->time_adjust) { + long borrow =3D div64_u64(deficit + ONE_US_NS - 1, ONE_US_NS); + + if (ntpdata->time_adjust > 0) { + borrow =3D min(borrow, ntpdata->time_adjust); + ntpdata->time_adjust -=3D borrow; + ntpdata->time_adjust_frac +=3D (s64)borrow * ONE_US_NS; + } else { + /* Clamp without negating time_adjust (UB for LONG_MIN) */ + if (ntpdata->time_adjust > -borrow) + borrow =3D -ntpdata->time_adjust; + ntpdata->time_adjust +=3D borrow; + ntpdata->time_adjust_frac -=3D (s64)borrow * ONE_US_NS; + } + } + + /* Never deliver more than the drawer holds */ + deliver =3D min(deliver, abs(ntpdata->time_adjust_frac)); + if (ntpdata->time_adjust_frac > 0) + ntpdata->time_adjust_frac -=3D deliver; + else + ntpdata->time_adjust_frac +=3D deliver; + + /* Return the unclaimed remainder in =C3=B7HZ drain units for time_offset */ + claimed =3D div_s64(deliver, NTP_INTERVAL_FREQ); + return amount - signof(amount) * claimed; +} + +/* * Drain one accumulation's worth of intentional skew as it is delivered. * * @amount is the total intentional per-tick skew for this accumulation - * (skew_delta << shift), in time_offset units (shifted_ns / HZ). Returns - * the amount actually claimed (same =C3=B7HZ units). + * (skew_delta << shift), in time_offset units (shifted_ns / HZ). The + * adjtime() linear share is taken from time_adjust first (capped at the + * MAX_TICKADJ rate, hence @shift), then the exponential remainder from + * time_offset. Returns the amount actually claimed (same =C3=B7HZ units). */ s64 ntp_drain_skew(unsigned int tkid, s64 amount, unsigned int shift) { - s64 unclaimed =3D ntp_drain_time_offset(tkid, amount); + s64 unclaimed =3D ntp_drain_time_adjust(tkid, amount, shift); + + unclaimed =3D ntp_drain_time_offset(tkid, unclaimed); =20 /* * Return the amount actually drained from the intentional - * phase offset in time_offset. + * phase offset in time_offset and/or time_adjust. */ return amount - unclaimed; } @@ -544,11 +619,13 @@ int second_overflow(unsigned int tkid, time64_t secs) * to zero, pass the bare minimum =C2=B11 to ensure that it *does* * actually drain completely to zero. It won't overshoot because * logarithmic_accumulation() only drains what it can from - * time_offset and the rest ends up in ntp_error which drives - * the selection of 'mult' immediately each tick. + * time_offset or time_adjust, and the rest ends up in ntp_error + * which drives the selection of 'mult' immediately each tick. */ - if (ntpdata->time_offset) { + if (ntpdata->time_offset || ntpdata->time_adjust || + ntpdata->time_adjust_frac) { s64 off_chunk =3D ntp_offset_chunk(ntpdata, ntpdata->time_offset); + s64 adj_chunk =3D 0, net; =20 /* * Once the exponential chunk rounds to zero, deliver the last @@ -558,34 +635,31 @@ int second_overflow(unsigned int tkid, time64_t secs) if (!off_chunk) off_chunk =3D ntpdata->time_offset; =20 - /* Reduce to per-tick, then floor. */ - ntpdata->skew_delta =3D div_s64(off_chunk, NTP_INTERVAL_FREQ); - if (!ntpdata->skew_delta) - ntpdata->skew_delta =3D signof(off_chunk); - } else { - ntpdata->skew_delta =3D 0; - } + if (ntpdata->time_adjust || ntpdata->time_adjust_frac) { + s64 adj; =20 - if (!ntpdata->time_adjust) - goto out; + if (ntpdata->time_adjust >=3D MAX_TICKADJ) + adj =3D MAX_TICKADJ * ONE_US_NS; + else if (ntpdata->time_adjust <=3D -MAX_TICKADJ) + adj =3D -MAX_TICKADJ * ONE_US_NS; + else + adj =3D ntpdata->time_adjust * ONE_US_NS + + ntpdata->time_adjust_frac; =20 - if (ntpdata->time_adjust > MAX_TICKADJ) { - ntpdata->time_adjust -=3D MAX_TICKADJ; - ntpdata->tick_length +=3D MAX_TICKADJ_SCALED; - goto out; - } + adj_chunk =3D div_s64(adj, NTP_INTERVAL_FREQ); + if (!adj_chunk) + adj_chunk =3D signof(ntpdata->time_adjust_frac); + } =20 - if (ntpdata->time_adjust < -MAX_TICKADJ) { - ntpdata->time_adjust +=3D MAX_TICKADJ; - ntpdata->tick_length -=3D MAX_TICKADJ_SCALED; - goto out; + /* Net is what the clock delivers; reduce to per-tick, then floor. */ + net =3D off_chunk + adj_chunk; + ntpdata->skew_delta =3D div_s64(net, NTP_INTERVAL_FREQ); + if (!ntpdata->skew_delta && net) + ntpdata->skew_delta =3D signof(net); + } else { + ntpdata->skew_delta =3D 0; } =20 - ntpdata->tick_length +=3D (s64)(ntpdata->time_adjust * NSEC_PER_USEC / NTP_= INTERVAL_FREQ) - << NTP_SCALE_SHIFT; - ntpdata->time_adjust =3D 0; - -out: return leap; } =20 @@ -878,6 +952,7 @@ int ntp_adjtimex(unsigned int tkid, struct __kernel_timex= *txc, const struct tim if (!(txc->modes & ADJ_OFFSET_READONLY)) { /* adjtime() is independent from ntp_adjtime() */ ntpdata->time_adjust =3D txc->offset; + ntpdata->time_adjust_frac =3D 0; ntp_update_frequency(ntpdata); =20 audit_ntp_set_old(ad, AUDIT_NTP_ADJUST, save_adjust); @@ -1119,6 +1194,7 @@ static void hardpps_update_phase(struct ntp_data *ntpda= ta, long error) NTP_INTERVAL_FREQ); /* Cancel running adjtime() */ ntpdata->time_adjust =3D 0; + ntpdata->time_adjust_frac =3D 0; } /* Update jitter */ ntpdata->pps_jitter +=3D (jitter - ntpdata->pps_jitter) >> PPS_INTMIN; diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 0e646e9..97db2e9 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -2611,7 +2611,7 @@ static u64 logarithmic_accumulation(struct timekeeper *= tk, u64 offset, /* * When skewing, do so by adjusting ntp_error to impart an extra * target delta into ntp_error per tick, limited to what can be - * drained from time_offset to avoid overshoot. + * drained from time_offset / time_adjust to avoid overshoot. * * The base 'mult' value was calculated with the skew taken into * account, such that the per-tick choice of 'mult' vs. 'mult+1'