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 D91FE3D8121; Tue, 7 Jul 2026 22:22:07 +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=1783462929; cv=none; b=qpCDN+0/dQZnK6WE+Vk+LaMljrrE3kHqE/4eNtRwECsf7Xklc8piRgxnjyJd54idekcFWKHBhw9tj00Lcjiy1RunyLIIHS5Y7IXl4eeQTQz6BYePBtHMlnr5tMId6ufjPbXro2rxzS2KWjD7Mgf/uavXYJVGUAG0dNxmN6RBTgU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783462929; c=relaxed/simple; bh=LjpPE3vlspzAdATeaafFbLi3iuKxMZjaSdwRRVhwdH4=; h=Date:From:To:Subject:Cc:In-Reply-To:References:MIME-Version: Message-ID:Content-Type; b=u7oo9iRWnrlllEmgltV8GhB5ehGRwpAt/WJRlq9RARInhFJSgcuJyudGtier7y8VbMet8TFIyR6Wn4o5dBRg5xJsdQmL5RPDsWQcc5Soytmz7NF4G8Mc4NFfTOO5xBxaUceTpPMoS6qevilDVwWKy9XX0XRuatJxTB9bbwxZLnw= 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=i1E05j9x; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=kDgksqNh; 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="i1E05j9x"; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="kDgksqNh" Date: Tue, 07 Jul 2026 22:22:04 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020; t=1783462925; 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=mjPVzoiB/u8AUwNOixepXyG5ZHogxzx/Yj0L/G4MMp0=; b=i1E05j9x5GT+IMXSzkYFHtgxzj7vp6aewBNGiWCHLO1XxRufmJXBRH9rUAipiXeATSYIKa Me1tPBaQH3wlRQ0rfpJ6RyAuwSqbwSIDl6uYhvHi7LowI3965gHc5W7Ce6f6SK+cFwUu9I SDX7P+klbPBfDpk4nCCQ+jFXtlfM7U6SZm5blhWsjodrb3izy855I79+XElGyxt7QLHaxN KcphETxwJCzolKlg8FxxDqx3duvlXO0cP1mYU84wAKN1frYDY067RZm3NxG1XevuoHgC8a z7L5ZOyrM0tyByzbTsa1ny6qEpX/RLpJRtY03XIuZupIsigyheEPc2nGk7oMuw== DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020e; t=1783462925; 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=mjPVzoiB/u8AUwNOixepXyG5ZHogxzx/Yj0L/G4MMp0=; b=kDgksqNhrgEQ7+t2whqL+3AzixbmIF9WqYFY8K98C2CYxx0QH207Wy0NB+kSrMEhaYhP5R apdDQQfNdEW6v8Aw== 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: Settle competing time_offset and time_adjust skew Cc: David Woodhouse , Thomas Gleixner , x86@kernel.org, linux-kernel@vger.kernel.org In-Reply-To: <20260621220051.1030462-7-dwmw2@infradead.org> References: <20260621220051.1030462-7-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: <178346292431.744054.8356679216010604092.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: c10c8c1ad90cb26d33688986c4f1b0b1081fa10c Gitweb: https://git.kernel.org/tip/c10c8c1ad90cb26d33688986c4f1b0b1081= fa10c Author: David Woodhouse AuthorDate: Sun, 21 Jun 2026 22:53:59 +01:00 Committer: Thomas Gleixner CommitterDate: Wed, 08 Jul 2026 00:17:27 +02:00 timekeeping: Settle competing time_offset and time_adjust skew time_offset (the exponential PLL phase slew) and time_adjust (the linear adjtime() slew) can be asked to move the clock in opposite directions. second_overflow() folds only their *net* into the per-tick skew_delta, so the cancelling overlap would never be drained from either tracker by the per-tick code =E2=80=94 and if they cancel exactly, skew_delta is zero and neither converges at all. Arguably we could just let one of them entirely cancel out the other immediately, but that would be a change in userspace-visible behaviour. Instead, preserve the existing behaviour by calculating the "conflict" portion between the opposing skew each second, and transferring that amount directly from one tracker to the other. Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Assisted-by: Kiro:claude-opus-4.8 Link: https://patch.msgid.link/20260621220051.1030462-7-dwmw2@infradead.org --- kernel/time/ntp.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+) diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c index 20d211b..73a47dd 100644 --- a/kernel/time/ntp.c +++ b/kernel/time/ntp.c @@ -514,6 +514,69 @@ s64 ntp_drain_skew(unsigned int tkid, s64 amount, unsign= ed int shift) return amount - unclaimed; } =20 +/* + * time_offset (drained exponentially) and time_adjust (drained linearly at = the + * MAX_TICKADJ rate) can be asked to slew the clock in opposite directions. + * second_overflow() only folds their *net* into skew_delta, so the cancelli= ng + * part would never be drained from either tracker via the per-tick code -- = and + * if they cancel exactly, skew_delta is zero and neither converges at all. + * + * Settle that cancelling phase directly between the two here. No clock moti= on + * results (the opposing slews annihilate), but both move toward zero so nei= ther + * stalls. @amount is the phase to take off time_offset, in its (=C3=B7HZ) u= nits and + * with its sign; the same real magnitude comes off time_adjust in the oppos= ite + * direction. Clamped so neither tracker is driven past zero. + */ +static void ntp_transfer_offset_adjust(struct ntp_data *ntpdata, s64 amount) +{ + s64 frac_delta, carry; + + /* + * Don't drain time_offset past zero. @amount shares its sign and is + * normally bounded below it by ntp_offset_chunk(), but the =C2=B11 skew_de= lta + * floor for a tiny time_offset can exceed it, so clamp. + */ + if (abs(amount) > abs(ntpdata->time_offset)) + amount =3D ntpdata->time_offset; + if (!amount) + return; + + /* + * Remove the matching phase from time_adjust, in plain shifted-ns. No + * clamp against time_adjust's zero is needed: @amount is bounded by the + * adjtime chunk, which second_overflow() never lets exceed time_adjust's + * own pending phase, so this cannot overshoot. + */ + frac_delta =3D amount * NTP_INTERVAL_FREQ; + + ntpdata->time_offset -=3D amount; + + /* Add the matching phase to time_adjust, carrying whole =C2=B5s (O(1)). */ + ntpdata->time_adjust_frac +=3D frac_delta; + if (ntpdata->time_adjust_frac >=3D ONE_US_NS || + ntpdata->time_adjust_frac <=3D -ONE_US_NS) { + carry =3D div64_s64(ntpdata->time_adjust_frac, ONE_US_NS); + ntpdata->time_adjust +=3D carry; + ntpdata->time_adjust_frac -=3D carry * ONE_US_NS; + } + + /* + * Keep time_adjust and its sub-=C2=B5s remainder the same sign. The + * truncating carry above can leave them opposed (e.g. +4 =C2=B5s paired + * with -250 ns), and ntp_drain_time_adjust() treats abs(time_adjust_frac) + * as same-direction drawer capacity -- an opposing remainder there makes + * it over-deliver phase that was never removed from the pile. Borrow or + * repay a single whole =C2=B5s to realign; the total phase is unchanged. + */ + if (ntpdata->time_adjust > 0 && ntpdata->time_adjust_frac < 0) { + ntpdata->time_adjust--; + ntpdata->time_adjust_frac +=3D ONE_US_NS; + } else if (ntpdata->time_adjust < 0 && ntpdata->time_adjust_frac > 0) { + ntpdata->time_adjust++; + ntpdata->time_adjust_frac -=3D ONE_US_NS; + } +} + /** * ntp_get_next_leap - Returns the next leapsecond in CLOCK_REALTIME ktime_t * @tkid: Timekeeper ID @@ -651,6 +714,18 @@ int second_overflow(unsigned int tkid, time64_t secs) adj_chunk =3D signof(ntpdata->time_adjust_frac); } =20 + /* + * If the two slews oppose, only their net would drive the + * per-tick drain, so the cancelling part would never drain from + * either tracker and an exact cancellation would stall both. + * Settle that overlap directly between them (no clock motion). + */ + if (off_chunk && adj_chunk && signof(off_chunk) !=3D signof(adj_chunk)) { + s64 conflict =3D min(abs(off_chunk), abs(adj_chunk)); + + ntp_transfer_offset_adjust(ntpdata, signof(off_chunk) * conflict); + } + /* Net is what the clock delivers; reduce to per-tick, then floor. */ net =3D off_chunk + adj_chunk; ntpdata->skew_delta =3D div_s64(net, NTP_INTERVAL_FREQ);