mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/3] time/jiffies: Fix usecs_to_jiffies() saturation and jiffies_to_clock_t() rounding
@ 2026-09-28  9:35 Shashank Mohan Jain
  2026-09-28  9:35 ` [PATCH 1/3] time/jiffies: Don't saturate usecs_to_jiffies() on a truncated limit Shashank Mohan Jain
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Shashank Mohan Jain @ 2026-09-28  9:35 UTC (permalink / raw)
  To: John Stultz, Thomas Gleixner
  Cc: Stephen Boyd, Miroslav Lichvar, Eric Dumazet, Joel Granados,
	linux-kernel

Two conversion helpers give wrong results for some HZ values:

1) usecs_to_jiffies() compares its input with jiffies_to_usecs(
   MAX_JIFFY_OFFSET), which is truncated to 32 bits. With HZ=300 every
   timeout from about 23.9 to 71.6 minutes becomes MAX_JIFFY_OFFSET
   (infinite); with HZ=100/250/1000 the top few thousand u32 values do.
   No u32 number of microseconds can reach MAX_JIFFY_OFFSET, so the
   check is dropped (and a u32 wrap it was masking is fixed). As a side
   effect, usecs_to_jiffies(<constant>) now folds to a constant when HZ
   does not divide USEC_PER_SEC, instead of calling jiffies_to_usecs().

2) jiffies_to_clock_t() and jiffies_64_to_clock_t() use the rounded
   TICK_NSEC when HZ does not divide NSEC_PER_SEC, so with HZ=300
   jiffies_to_clock_t(300) is 99 and values written in USER_HZ do not
   read back (bridge forward_delay reads 1499, neigh locktime reads 99).
   They now use the nominal HZ ratio, like clock_t_to_jiffies(). This
   also fixes the conversion on alpha (USER_HZ=1024) for large values.

Patch 3 adds HZ-independent KUnit tests for both to
kernel/time/time_test.c.

Testing:
- KUnit, filter "time_test_cases.*jiffies*", on master plus patch 3
  and on the whole series: UML x86_64 (HZ=100), and qemu x86_64 and
  i386 (TCG) with CONFIG_HZ_300=y. Without the fixes the usecs test
  fails on all three and the clock_t test fails on both HZ=300 runs;
  with them both tests pass everywhere. On UML x86_64 with HZ/USER_HZ
  temporarily edited, the series passes at 24/100, 1024/100, 1200/100,
  32/1024, 1024/1024 and 1200/1024, and master plus patch 3 fails both
  tests at 1200/100 and 1024/1024.
- A userspace harness that extracts the conversion code verbatim from
  the tree, both master and this series, and builds it with the
  generated timeconst.h for every Kconfig HZ value (24, 32, 48, 64,
  100, 128, 200, 250, 256, 300, 500, 1000, 1024 and 1200), USER_HZ 100
  and 1024, 32- and 64-bit. On master it shows both problems. With the
  series, usecs_to_jiffies() over all 2^32 inputs never saturates, is
  never below the exact rounded-up value and is at most one jiffy above
  it (only where HZ does not divide USEC_PER_SEC, as before).
  jiffies_to_clock_t() and jiffies_64_to_clock_t() equal
  floor(x * USER_HZ / HZ) for every x below 5e7 and invert
  clock_t_to_jiffies() for every clock_t value below 2e7 that is a
  whole number of jiffies. An earlier harness, with the fixed
  expressions retyped rather than extracted, also checked
  jiffies_to_clock_t() for every x below 4e8 with USER_HZ=100.
- An x86_64 defconfig kernel with HZ=300 and CONFIG_BRIDGE=y booted in
  qemu, with a small init that creates a bridge and reads the values
  back. On master: forward_delay 1499, hello_time 199, max_age 1999,
  ageing_time 29999, ipv4 neigh retrans_time/anycast_delay/locktime 99,
  proxy_delay 79, ipv6 neigh anycast_delay 99, and writing 1500 to
  forward_delay and then writing back what was read gives 1499, 1498,
  1497; writing 100 to ipv4 locktime reads back 99. With the series:
  1500, 200, 2000, 30000, 100, 80, 100, 1500 each time, and 100. ipv6
  neigh retrans_time reads 300 on both, as expected (raw jiffies).
- W=1 builds of kernel/time/{time,time_test,jiffies,timer}.o,
  net/bridge/{br_sysfs_br,br_netlink}.o and net/core/neighbour.o for
  x86_64 and i386 defconfig at HZ=300 and HZ=1000: no warnings before
  or after, and no libgcc 64-bit division helpers in time.o on i386.
- checkpatch --strict: clean once the Signed-off-by is added (checked
  with one appended).

Not tested: real hardware, and architectures other than x86 (alpha's
USER_HZ=1024 only through the edited UML builds and the harness).

jiffies_to_msecs() on 32-bit also rounds exact values up by one for
HZ=300 (jiffies_to_msecs(3) == 11); that is left for a separate patch.
The series does not overlap with "time/jiffies: Saturate in mult_hz()
instead of wrapping", which is also under review and touches
kernel/time/jiffies.c.

This series was prepared with Claude Code (Anthropic), model Claude
Opus 5.5 (claude-opus-5-5), which found the bugs with the harness above
and a Lean 4 model of the arithmetic, and wrote the patches and tests.
A separate Claude Code session reviewed the series independently, and
the changelogs and tests were revised after that review.

Shashank Mohan Jain (3):
  time/jiffies: Don't saturate usecs_to_jiffies() on a truncated limit
  time/jiffies: Use the exact HZ ratio in jiffies_to_clock_t()
  time/kunit: Add tests for usecs_to_jiffies() and jiffies_to_clock_t()

 include/linux/jiffies.h | 22 +++++-------
 kernel/time/time.c      | 24 +++++++------
 kernel/time/time_test.c | 79 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 101 insertions(+), 24 deletions(-)

-- 
2.43.0


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

* [PATCH 1/3] time/jiffies: Don't saturate usecs_to_jiffies() on a truncated limit
  2026-09-28  9:35 [PATCH 0/3] time/jiffies: Fix usecs_to_jiffies() saturation and jiffies_to_clock_t() rounding Shashank Mohan Jain
@ 2026-09-28  9:35 ` Shashank Mohan Jain
  2026-09-28 11:38   ` Joel Granados
  2026-09-28  9:36 ` [PATCH 2/3] time/jiffies: Use the exact HZ ratio in jiffies_to_clock_t() Shashank Mohan Jain
  2026-09-28  9:36 ` [PATCH 3/3] time/kunit: Add tests for usecs_to_jiffies() and jiffies_to_clock_t() Shashank Mohan Jain
  2 siblings, 1 reply; 6+ messages in thread
From: Shashank Mohan Jain @ 2026-09-28  9:35 UTC (permalink / raw)
  To: John Stultz, Thomas Gleixner
  Cc: Stephen Boyd, Miroslav Lichvar, Eric Dumazet, Joel Granados,
	linux-kernel

usecs_to_jiffies() and __usecs_to_jiffies() return MAX_JIFFY_OFFSET, an
effectively infinite timeout, when

	u > jiffies_to_usecs(MAX_JIFFY_OFFSET)

but jiffies_to_usecs() returns an unsigned int, so the limit is
MAX_JIFFY_OFFSET microseconds truncated to 32 bits, an arbitrary value.
With HZ=300 the out-of-line jiffies_to_usecs() also overflows 64 bits in
j * HZ_TO_USEC_NUM, and the limit ends up at 1431649098 us on 64-bit and
1431649781 us on 32-bit. Every timeout between about 23.9 and 71.6
minutes then becomes infinite: usecs_to_jiffies(2000000000) returns
MAX_JIFFY_OFFSET instead of 600000. For instance, a PIE tupdate or a
DAMOS watermark interval of 30 minutes never fires at HZ=300. With
HZ=100, 250 and 1000 the limit is 4294947296, 4294959296 and 4294965296
us, so there only the top 19999, 7999 and 1999 values are affected.

The check is not needed at all. MAX_JIFFY_OFFSET is at least 2^30 - 2
jiffies and HZ is below 12288, so UINT_MAX microseconds are far below
MAX_JIFFY_OFFSET jiffies for every supported HZ. Drop it, update the
kernel-doc, and add a static_assert() for the assumption.

The bogus limit was also hiding a wrap: when HZ divides USEC_PER_SEC,
_usecs_to_jiffies() rounds up with (u + USEC_PER_SEC / HZ - 1) in
unsigned int, which wraps for u close to UINT_MAX (usecs_to_jiffies(
UINT_MAX) would become 0 at HZ=100). Round up with a remainder test
instead. The reciprocal multiplication used for the other HZ values is
done in 64 bits and does not wrap for any 32-bit input.

Values above the old limit, including UINT_MAX, now give a finite
timeout (at most about 71.6 minutes) with every HZ. No caller uses
UINT_MAX as an "infinite" marker; the callers that clamp to UINT_MAX or
range-check the result (TCP_RTO_MIN_US, TCP_DELACK_MAX_US) keep working.

When HZ does not divide USEC_PER_SEC, jiffies_to_usecs() is out of line,
so the check also defeated the constant folding: every
usecs_to_jiffies(<constant>) compiled to a call to jiffies_to_usecs()
and a compare. Those now fold to a constant.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Assisted-by: LLM
Signed-off-by: Shashank Mohan Jain <jain.sm@gmail.com>
---
Checked in userspace over all 2^32 inputs, with the current code and
the code from this patch both extracted verbatim from the tree and
built with the generated timeconst.h, for every Kconfig HZ value (24,
32, 48, 64, 100, 128, 200, 250, 256, 300, 500, 1000, 1024, 1200),
32- and 64-bit. The KUnit test in patch 3 fails without this patch on
UML x86_64 (HZ=100) and on qemu x86_64 and i386 with HZ=300, and passes
with it. Prepared with Claude Code (Anthropic), model Claude Opus 5.5
(claude-opus-5-5).

 include/linux/jiffies.h | 22 ++++++++--------------
 kernel/time/time.c      | 10 +++++++---
 2 files changed, 15 insertions(+), 17 deletions(-)

diff --git a/include/linux/jiffies.h b/include/linux/jiffies.h
index bbd57061802c..d3f2663e4435 100644
--- a/include/linux/jiffies.h
+++ b/include/linux/jiffies.h
@@ -575,7 +575,8 @@ extern unsigned long __usecs_to_jiffies(const unsigned int u);
 #if !(USEC_PER_SEC % HZ)
 static inline unsigned long _usecs_to_jiffies(const unsigned int u)
 {
-	return (u + (USEC_PER_SEC / HZ) - 1) / (USEC_PER_SEC / HZ);
+	/* Round up without overflowing for u close to UINT_MAX */
+	return u / (USEC_PER_SEC / HZ) + !!(u % (USEC_PER_SEC / HZ));
 }
 #else
 static inline unsigned long _usecs_to_jiffies(const unsigned int u)
@@ -589,14 +590,10 @@ static inline unsigned long _usecs_to_jiffies(const unsigned int u)
  * usecs_to_jiffies: - convert microseconds to jiffies
  * @u:	time in microseconds
  *
- * conversion is done as follows:
- *
- * - 'too large' values [that would result in larger than
- *   MAX_JIFFY_OFFSET values] mean 'infinite timeout' too.
- *
- * - all other values are converted to jiffies by either multiplying
- *   the input value by a factor or dividing it with a factor and
- *   handling any 32-bit overflows as for msecs_to_jiffies.
+ * conversion is done by either multiplying the input value by a factor
+ * or dividing it with a factor, rounding up. Any unsigned int number of
+ * microseconds is far below MAX_JIFFY_OFFSET jiffies for every supported
+ * HZ, so, unlike msecs_to_jiffies(), there is no 'infinite timeout' case.
  *
  * usecs_to_jiffies() checks for the passed in value being a constant
  * via __builtin_constant_p() allowing gcc to eliminate most of the
@@ -611,13 +608,10 @@ static inline unsigned long _usecs_to_jiffies(const unsigned int u)
  */
 static __always_inline unsigned long usecs_to_jiffies(const unsigned int u)
 {
-	if (__builtin_constant_p(u)) {
-		if (u > jiffies_to_usecs(MAX_JIFFY_OFFSET))
-			return MAX_JIFFY_OFFSET;
+	if (__builtin_constant_p(u))
 		return _usecs_to_jiffies(u);
-	} else {
+	else
 		return __usecs_to_jiffies(u);
-	}
 }
 
 extern unsigned long timespec64_to_jiffies(const struct timespec64 *value);
diff --git a/kernel/time/time.c b/kernel/time/time.c
index 079ab34f61db..470195e88ca0 100644
--- a/kernel/time/time.c
+++ b/kernel/time/time.c
@@ -591,16 +591,20 @@ unsigned long __msecs_to_jiffies(const unsigned int m)
 }
 EXPORT_SYMBOL(__msecs_to_jiffies);
 
+/*
+ * UINT_MAX microseconds are far less than MAX_JIFFY_OFFSET jiffies for every
+ * supported HZ, so usecs_to_jiffies() never needs to saturate.
+ */
+static_assert((u64)UINT_MAX * HZ / USEC_PER_SEC < MAX_JIFFY_OFFSET);
+
 /**
  * __usecs_to_jiffies: - convert microseconds to jiffies
- * @u:	time in milliseconds
+ * @u:	time in microseconds
  *
  * Return: jiffies value
  */
 unsigned long __usecs_to_jiffies(const unsigned int u)
 {
-	if (u > jiffies_to_usecs(MAX_JIFFY_OFFSET))
-		return MAX_JIFFY_OFFSET;
 	return _usecs_to_jiffies(u);
 }
 EXPORT_SYMBOL(__usecs_to_jiffies);
-- 
2.43.0


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

* [PATCH 2/3] time/jiffies: Use the exact HZ ratio in jiffies_to_clock_t()
  2026-09-28  9:35 [PATCH 0/3] time/jiffies: Fix usecs_to_jiffies() saturation and jiffies_to_clock_t() rounding Shashank Mohan Jain
  2026-09-28  9:35 ` [PATCH 1/3] time/jiffies: Don't saturate usecs_to_jiffies() on a truncated limit Shashank Mohan Jain
@ 2026-09-28  9:36 ` Shashank Mohan Jain
  2026-09-28  9:36 ` [PATCH 3/3] time/kunit: Add tests for usecs_to_jiffies() and jiffies_to_clock_t() Shashank Mohan Jain
  2 siblings, 0 replies; 6+ messages in thread
From: Shashank Mohan Jain @ 2026-09-28  9:36 UTC (permalink / raw)
  To: John Stultz, Thomas Gleixner
  Cc: Stephen Boyd, Miroslav Lichvar, Eric Dumazet, Joel Granados,
	linux-kernel

When TICK_NSEC is not a multiple of NSEC_PER_SEC / USER_HZ,
jiffies_to_clock_t() and jiffies_64_to_clock_t() convert with

	div_u64((u64)x * TICK_NSEC, NSEC_PER_SEC / USER_HZ)

TICK_NSEC is NSEC_PER_SEC / HZ rounded to the nearest nanosecond. When
HZ does not divide NSEC_PER_SEC it is slightly short (3333333 ns at
HZ=300), so a number of jiffies that is exactly a whole number of clock
ticks converts to just below it and is truncated to one tick less. With
HZ=300 and USER_HZ=100, jiffies_to_clock_t(300) is 99, and every
multiple of 3 jiffies is one tick short.

The tick does advance jiffies every TICK_NSEC, so as a measure of
elapsed time the old result is accurate to about 1e-7. But intervals
that userspace sets in clock ticks are stored with clock_t_to_jiffies(),
which uses the nominal HZ ratio, as do jiffies_to_msecs() and
jiffies_to_usecs(), and they no longer read back. With HZ=300 the bridge
defaults read as forward_delay 1499, hello_time 199, max_age 1999 and
ageing_time 29999 through sysfs and netlink, and each read-modify-write
through sysfs loses one more tick (1500 -> 1499 -> 1498). The neighbour
sysctls that use proc_dointvec_userhz_jiffies() behave the same way:
net.ipv4.neigh.*.retrans_time, anycast_delay and locktime read 99
instead of 100, proxy_delay reads 79 instead of 80, and
net.ipv6.neigh.*.anycast_delay reads 99. Inside the kernel, IPv6 route
lifetimes from router advertisements and netlink go through
jiffies_to_clock_t() and back through clock_t_to_jiffies(), so at
HZ=300 those routes expire 3 jiffies early.

Convert with the nominal ratio, x * USER_HZ / HZ, like
clock_t_to_jiffies() does. With USER_HZ=100 the result does not change
where TICK_NSEC is exact (e.g. HZ=250). On alpha, where USER_HZ is 1024,
NSEC_PER_SEC / USER_HZ is itself truncated and the old code returned one
tick too many for large values (jiffies_to_clock_t(976562) == 976563
with HZ=1024); that is fixed too. The value returned by times() now
advances by exactly USER_HZ ticks per HZ jiffies. The product also
overflows much later than x * TICK_NSEC did in jiffies_64_to_clock_t().

The TICK_NSEC based conversion predates git. It made sense while
TICK_NSEC was the tick length measured from CLOCK_TICK_RATE, when x86
returned 99 for 300 jiffies at HZ=300 and for 1000 jiffies at HZ=1000.
Commit b3c869d35b9b ("jiffies: Remove compile time assumptions about
CLOCK_TICK_RATE") made TICK_NSEC exact for HZ values that divide
NSEC_PER_SEC, but left the others rounded.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Assisted-by: LLM
Signed-off-by: Shashank Mohan Jain <jain.sm@gmail.com>
---
The KUnit test in patch 3 fails without this patch on qemu x86_64 and
i386 with HZ=300 and passes with it; UML (HZ=100) is unaffected. It
also passes on UML x86_64 with HZ/USER_HZ temporarily edited to
1200/100 and 1024/1024, both of which fail without the series. In
userspace, with the code extracted verbatim, both functions equal
floor(x * USER_HZ / HZ) for every x below 5e7 and invert
clock_t_to_jiffies() for every whole-jiffy clock_t value below 2e7, for
every Kconfig HZ value with USER_HZ 100 and 1024, 32- and 64-bit. The
bridge and neighbour values above were read back on an x86_64 HZ=300
kernel in qemu, before and after this series.

Possible follow-up, not part of this series:
tools/testing/selftests/bpf/progs/bpf_iter_tcp4.c and bpf_iter_tcp6.c
carry a copy of the old TICK_NSEC formula so that they print the same
values as /proc/net/tcp. The test only does a dummy read, so nothing
breaks, but at HZ=300 the copies no longer match the kernel.

Prepared with Claude Code (Anthropic), model Claude Opus 5.5
(claude-opus-5-5).

 kernel/time/time.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/kernel/time/time.c b/kernel/time/time.c
index 470195e88ca0..f19e742c9562 100644
--- a/kernel/time/time.c
+++ b/kernel/time/time.c
@@ -684,7 +684,11 @@ clock_t jiffies_to_clock_t(unsigned long x)
 	return x / (HZ / USER_HZ);
 # endif
 #else
-	return div_u64((u64)x * TICK_NSEC, NSEC_PER_SEC / USER_HZ);
+	/*
+	 * Use the nominal HZ ratio like clock_t_to_jiffies(); TICK_NSEC is
+	 * rounded when HZ does not divide NSEC_PER_SEC.
+	 */
+	return div_u64((u64)x * USER_HZ, HZ);
 #endif
 }
 EXPORT_SYMBOL(jiffies_to_clock_t);
@@ -729,12 +733,8 @@ notrace u64 jiffies_64_to_clock_t(u64 x)
 	/* Nothing to do */
 # endif
 #else
-	/*
-	 * There are better ways that don't overflow early,
-	 * but even this doesn't overflow in hundreds of years
-	 * in 64 bits, so..
-	 */
-	x = div_u64(x * TICK_NSEC, (NSEC_PER_SEC / USER_HZ));
+	/* Nominal HZ ratio, see jiffies_to_clock_t() */
+	x = div_u64(x * USER_HZ, HZ);
 #endif
 	return x;
 }
-- 
2.43.0


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

* [PATCH 3/3] time/kunit: Add tests for usecs_to_jiffies() and jiffies_to_clock_t()
  2026-09-28  9:35 [PATCH 0/3] time/jiffies: Fix usecs_to_jiffies() saturation and jiffies_to_clock_t() rounding Shashank Mohan Jain
  2026-09-28  9:35 ` [PATCH 1/3] time/jiffies: Don't saturate usecs_to_jiffies() on a truncated limit Shashank Mohan Jain
  2026-09-28  9:36 ` [PATCH 2/3] time/jiffies: Use the exact HZ ratio in jiffies_to_clock_t() Shashank Mohan Jain
@ 2026-09-28  9:36 ` Shashank Mohan Jain
  2 siblings, 0 replies; 6+ messages in thread
From: Shashank Mohan Jain @ 2026-09-28  9:36 UTC (permalink / raw)
  To: John Stultz, Thomas Gleixner
  Cc: Stephen Boyd, Miroslav Lichvar, Eric Dumazet, Joel Granados,
	linux-kernel

Check that jiffies_to_clock_t() and jiffies_64_to_clock_t() invert
clock_t_to_jiffies() for every clock_t value that is a whole number of
jiffies, and that usecs_to_jiffies() rounds up and never saturates for
any unsigned int input, both through the out-of-line and the
constant-folded path.

The tests do not depend on HZ. With HZ=100 (UML) the usecs_to_jiffies()
test fails without the preceding fix for the values close to UINT_MAX,
and with HZ=300 both tests fail without the preceding fixes. Each loop
stops at its first failure, so a failing run reports a handful of
values instead of thousands.

Assisted-by: LLM
Signed-off-by: Shashank Mohan Jain <jain.sm@gmail.com>
---
Run with kunit.py, filter "time_test_cases.*jiffies*", with and without
patches 1-2, on UML x86_64 (HZ=100) and on qemu x86_64 and i386 (TCG)
with CONFIG_HZ_300=y. Also on UML x86_64 with HZ/USER_HZ temporarily
edited: with patches 1-2 both tests pass at 24/100, 1024/100, 1200/100,
32/1024, 1024/1024 and 1200/1024; without them both fail at 1200/100
and 1024/1024.
Without the fixes, the HZ=300 x86_64 run now reports 15 failed
expectations (4 KB of output) instead of about 2,900. Prepared with
Claude Code (Anthropic), model Claude Opus 5.5 (claude-opus-5-5).

 kernel/time/time_test.c | 79 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 79 insertions(+)

diff --git a/kernel/time/time_test.c b/kernel/time/time_test.c
index 1b99180da288..77e1ec1d5d8b 100644
--- a/kernel/time/time_test.c
+++ b/kernel/time/time_test.c
@@ -1,6 +1,9 @@
 // SPDX-License-Identifier: LGPL-2.1+
 
 #include <kunit/test.h>
+#include <linux/gcd.h>
+#include <linux/jiffies.h>
+#include <linux/math64.h>
 #include <linux/time.h>
 
 /*
@@ -87,8 +90,84 @@ static void time64_to_tm_test_date_range(struct kunit *test)
 	}
 }
 
+/*
+ * jiffies_to_clock_t() must be the inverse of clock_t_to_jiffies() for every
+ * clock_t value that is a whole number of jiffies, whatever HZ is.
+ */
+static void jiffies_to_clock_t_test(struct kunit *test)
+{
+	unsigned long step = USER_HZ / gcd(HZ, USER_HZ);
+	unsigned long x;
+
+	/* Bridge STP defaults, exposed in USER_HZ through sysfs and netlink */
+	KUNIT_EXPECT_EQ(test, 15 * USER_HZ, jiffies_to_clock_t(15 * HZ));
+	KUNIT_EXPECT_EQ(test, 2 * USER_HZ, jiffies_to_clock_t(2 * HZ));
+	KUNIT_EXPECT_EQ(test, 300 * USER_HZ, jiffies_to_clock_t(300 * HZ));
+	/* Large enough to catch a rounded NSEC_PER_SEC / USER_HZ (alpha) */
+	KUNIT_EXPECT_EQ(test, 10000 * USER_HZ, jiffies_to_clock_t(10000 * HZ));
+
+	for (x = 0; x <= 100 * USER_HZ; x += step) {
+		unsigned long j = clock_t_to_jiffies(x);
+		clock_t c = jiffies_to_clock_t(j);
+		u64 c64 = jiffies_64_to_clock_t(j);
+
+		KUNIT_ASSERT_EQ_MSG(test, x * HZ, (u64)j * USER_HZ,
+				    "clock_t %lu -> %lu jiffies", x, j);
+		if (c != (long)x || c64 != x) {
+			KUNIT_FAIL(test, "clock_t %lu -> %lu jiffies -> %ld / %llu",
+				   x, j, (long)c, c64);
+			break;
+		}
+	}
+}
+
+/*
+ * Every unsigned int number of microseconds fits in MAX_JIFFY_OFFSET jiffies
+ * for any supported HZ, so usecs_to_jiffies() must never saturate. It must
+ * round up; when HZ does not divide USEC_PER_SEC, the reciprocal
+ * multiplication may round up by one more jiffy for large values.
+ */
+static bool check_usecs_to_jiffies(struct kunit *test, unsigned int u,
+				   unsigned long got)
+{
+	unsigned long want = DIV_ROUND_UP_ULL((u64)u * HZ, USEC_PER_SEC);
+
+	if (got >= want && got <= want + !!(USEC_PER_SEC % HZ))
+		return true;
+	KUNIT_FAIL(test, "usecs_to_jiffies(%u) = %lu, want %lu%s", u, got,
+		   want, USEC_PER_SEC % HZ ? " or one more" : "");
+	return false;
+}
+
+static void usecs_to_jiffies_test(struct kunit *test)
+{
+	static const unsigned int vals[] = {
+		0, 1, 999, 1000, 1001, 1000000, 1431649098, 1431649099,
+		2000000000, 3000000000U, UINT_MAX - 20000, UINT_MAX - 1,
+		UINT_MAX,
+	};
+	u64 x;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(vals); i++)
+		check_usecs_to_jiffies(test, vals[i], usecs_to_jiffies(vals[i]));
+
+	for (x = 0; x <= UINT_MAX; x += 999983)
+		if (!check_usecs_to_jiffies(test, x, usecs_to_jiffies(x)))
+			break;
+	for (x = UINT_MAX - 30000; x <= UINT_MAX; x++)
+		if (!check_usecs_to_jiffies(test, x, usecs_to_jiffies(x)))
+			break;
+
+	/* Constant-folded variant */
+	check_usecs_to_jiffies(test, 3000000000U, usecs_to_jiffies(3000000000U));
+	check_usecs_to_jiffies(test, UINT_MAX, usecs_to_jiffies(UINT_MAX));
+}
+
 static struct kunit_case time_test_cases[] = {
 	KUNIT_CASE_SLOW(time64_to_tm_test_date_range),
+	KUNIT_CASE(jiffies_to_clock_t_test),
+	KUNIT_CASE(usecs_to_jiffies_test),
 	{}
 };
 
-- 
2.43.0


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

* Re: [PATCH 1/3] time/jiffies: Don't saturate usecs_to_jiffies() on a truncated limit
  2026-09-28  9:35 ` [PATCH 1/3] time/jiffies: Don't saturate usecs_to_jiffies() on a truncated limit Shashank Mohan Jain
@ 2026-09-28 11:38   ` Joel Granados
  2026-09-28 12:09     ` shashank Jain
  0 siblings, 1 reply; 6+ messages in thread
From: Joel Granados @ 2026-09-28 11:38 UTC (permalink / raw)
  To: Shashank Mohan Jain
  Cc: John Stultz, Thomas Gleixner, Stephen Boyd, Miroslav Lichvar,
	Eric Dumazet, linux-kernel

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

On Mon, Sep 28, 2026 at 03:05:59PM +0530, Shashank Mohan Jain wrote:
> usecs_to_jiffies() and __usecs_to_jiffies() return MAX_JIFFY_OFFSET, an
> effectively infinite timeout, when
> 
> 	u > jiffies_to_usecs(MAX_JIFFY_OFFSET)
> 
> but jiffies_to_usecs() returns an unsigned int, so the limit is
> MAX_JIFFY_OFFSET microseconds truncated to 32 bits, an arbitrary value.
> With HZ=300 the out-of-line jiffies_to_usecs() also overflows 64 bits in
> j * HZ_TO_USEC_NUM, and the limit ends up at 1431649098 us on 64-bit and
> 1431649781 us on 32-bit. Every timeout between about 23.9 and 71.6
> minutes then becomes infinite: usecs_to_jiffies(2000000000) returns
> MAX_JIFFY_OFFSET instead of 600000. For instance, a PIE tupdate or a
> DAMOS watermark interval of 30 minutes never fires at HZ=300. With
> HZ=100, 250 and 1000 the limit is 4294947296, 4294959296 and 4294965296
> us, so there only the top 19999, 7999 and 1999 values are affected.
> 
> The check is not needed at all. MAX_JIFFY_OFFSET is at least 2^30 - 2
> jiffies and HZ is below 12288, so UINT_MAX microseconds are far below
> MAX_JIFFY_OFFSET jiffies for every supported HZ. Drop it, update the
> kernel-doc, and add a static_assert() for the assumption.
> 
> The bogus limit was also hiding a wrap: when HZ divides USEC_PER_SEC,
> _usecs_to_jiffies() rounds up with (u + USEC_PER_SEC / HZ - 1) in
> unsigned int, which wraps for u close to UINT_MAX (usecs_to_jiffies(
> UINT_MAX) would become 0 at HZ=100). Round up with a remainder test
> instead. The reciprocal multiplication used for the other HZ values is
> done in 64 bits and does not wrap for any 32-bit input.

Looks like your fixing two things in one patch. I would suggest making
two patches

> 
> Values above the old limit, including UINT_MAX, now give a finite
> timeout (at most about 71.6 minutes) with every HZ. No caller uses
> UINT_MAX as an "infinite" marker; the callers that clamp to UINT_MAX or
> range-check the result (TCP_RTO_MIN_US, TCP_DELACK_MAX_US) keep working.
> 
> When HZ does not divide USEC_PER_SEC, jiffies_to_usecs() is out of line,
> so the check also defeated the constant folding: every
> usecs_to_jiffies(<constant>) compiled to a call to jiffies_to_usecs()
> and a compare. Those now fold to a constant.

This commit message (and the others in this series as well as the cover
letter) is too verbose, hard to read. It is not obvious what you are
doing and why.

I suggest a table of erroneous values. Like [1]

[1] https://lore.kernel.org/20260923134237.3628505-1-zhanxusheng@xiaomi.com
> 
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Assisted-by: LLM
> Signed-off-by: Shashank Mohan Jain <jain.sm@gmail.com>
> ---
> Checked in userspace over all 2^32 inputs, with the current code and
> the code from this patch both extracted verbatim from the tree and
> built with the generated timeconst.h, for every Kconfig HZ value (24,
> 32, 48, 64, 100, 128, 200, 250, 256, 300, 500, 1000, 1024, 1200),
> 32- and 64-bit. The KUnit test in patch 3 fails without this patch on
> UML x86_64 (HZ=100) and on qemu x86_64 and i386 with HZ=300, and passes
> with it. Prepared with Claude Code (Anthropic), model Claude Opus 5.5
> (claude-opus-5-5).
> 
>  include/linux/jiffies.h | 22 ++++++++--------------
>  kernel/time/time.c      | 10 +++++++---
>  2 files changed, 15 insertions(+), 17 deletions(-)
> 
> diff --git a/include/linux/jiffies.h b/include/linux/jiffies.h
> index bbd57061802c..d3f2663e4435 100644
> --- a/include/linux/jiffies.h
> +++ b/include/linux/jiffies.h
> @@ -575,7 +575,8 @@ extern unsigned long __usecs_to_jiffies(const unsigned int u);
>  #if !(USEC_PER_SEC % HZ)
>  static inline unsigned long _usecs_to_jiffies(const unsigned int u)
>  {
> -	return (u + (USEC_PER_SEC / HZ) - 1) / (USEC_PER_SEC / HZ);
> +	/* Round up without overflowing for u close to UINT_MAX */
> +	return u / (USEC_PER_SEC / HZ) + !!(u % (USEC_PER_SEC / HZ));
>  }
>  #else
>  static inline unsigned long _usecs_to_jiffies(const unsigned int u)
> @@ -589,14 +590,10 @@ static inline unsigned long _usecs_to_jiffies(const unsigned int u)
>   * usecs_to_jiffies: - convert microseconds to jiffies
>   * @u:	time in microseconds
>   *
> - * conversion is done as follows:
> - *
> - * - 'too large' values [that would result in larger than
> - *   MAX_JIFFY_OFFSET values] mean 'infinite timeout' too.
> - *
> - * - all other values are converted to jiffies by either multiplying
> - *   the input value by a factor or dividing it with a factor and
> - *   handling any 32-bit overflows as for msecs_to_jiffies.
> + * conversion is done by either multiplying the input value by a factor
> + * or dividing it with a factor, rounding up. Any unsigned int number of
> + * microseconds is far below MAX_JIFFY_OFFSET jiffies for every supported
> + * HZ, so, unlike msecs_to_jiffies(), there is no 'infinite timeout' case.
>   *
>   * usecs_to_jiffies() checks for the passed in value being a constant
>   * via __builtin_constant_p() allowing gcc to eliminate most of the
> @@ -611,13 +608,10 @@ static inline unsigned long _usecs_to_jiffies(const unsigned int u)
>   */
>  static __always_inline unsigned long usecs_to_jiffies(const unsigned int u)
>  {
> -	if (__builtin_constant_p(u)) {
> -		if (u > jiffies_to_usecs(MAX_JIFFY_OFFSET))
> -			return MAX_JIFFY_OFFSET;
> +	if (__builtin_constant_p(u))
>  		return _usecs_to_jiffies(u);
> -	} else {
> +	else
>  		return __usecs_to_jiffies(u);
> -	}

This effectively becomes

if (__builtin_constant_p(u))
  return _usecs_to_jiffies(u);
else
  return _usecs_to_jiffies(u);

What is the point of this?

Best
>  
>  extern unsigned long timespec64_to_jiffies(const struct timespec64 *value);
> diff --git a/kernel/time/time.c b/kernel/time/time.c
> index 079ab34f61db..470195e88ca0 100644
> --- a/kernel/time/time.c
> +++ b/kernel/time/time.c
> @@ -591,16 +591,20 @@ unsigned long __msecs_to_jiffies(const unsigned int m)
>  }
>  EXPORT_SYMBOL(__msecs_to_jiffies);
>  
> +/*
> + * UINT_MAX microseconds are far less than MAX_JIFFY_OFFSET jiffies for every
> + * supported HZ, so usecs_to_jiffies() never needs to saturate.
> + */
> +static_assert((u64)UINT_MAX * HZ / USEC_PER_SEC < MAX_JIFFY_OFFSET);
> +
>  /**
>   * __usecs_to_jiffies: - convert microseconds to jiffies
> - * @u:	time in milliseconds
> + * @u:	time in microseconds
>   *
>   * Return: jiffies value
>   */
>  unsigned long __usecs_to_jiffies(const unsigned int u)
>  {
> -	if (u > jiffies_to_usecs(MAX_JIFFY_OFFSET))
> -		return MAX_JIFFY_OFFSET;
>  	return _usecs_to_jiffies(u);
>  }
>  EXPORT_SYMBOL(__usecs_to_jiffies);
> -- 
> 2.43.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 659 bytes --]

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

* Re: [PATCH 1/3] time/jiffies: Don't saturate usecs_to_jiffies() on a truncated limit
  2026-09-28 11:38   ` Joel Granados
@ 2026-09-28 12:09     ` shashank Jain
  0 siblings, 0 replies; 6+ messages in thread
From: shashank Jain @ 2026-09-28 12:09 UTC (permalink / raw)
  To: Joel Granados
  Cc: John Stultz, Thomas Gleixner, Stephen Boyd, Miroslav Lichvar,
	Eric Dumazet, linux-kernel

Thanks for the review.

Agreed on all three points. For v2 I'll split patch 1 into the removal
of the bogus limit and the rounding fix, drop the now-pointless
__builtin_constant_p() branch in usecs_to_jiffies(), and rewrite the
changelogs and the cover letter much shorter, with a table of the wrong
values as in Zhan's patch.

I'll wait a few days for other comments before sending v2.

Shashank


On Mon, Sep 28, 2026 at 5:08 PM Joel Granados <joel.granados@kernel.org> wrote:
>
> On Mon, Sep 28, 2026 at 03:05:59PM +0530, Shashank Mohan Jain wrote:
> > usecs_to_jiffies() and __usecs_to_jiffies() return MAX_JIFFY_OFFSET, an
> > effectively infinite timeout, when
> >
> >       u > jiffies_to_usecs(MAX_JIFFY_OFFSET)
> >
> > but jiffies_to_usecs() returns an unsigned int, so the limit is
> > MAX_JIFFY_OFFSET microseconds truncated to 32 bits, an arbitrary value.
> > With HZ=300 the out-of-line jiffies_to_usecs() also overflows 64 bits in
> > j * HZ_TO_USEC_NUM, and the limit ends up at 1431649098 us on 64-bit and
> > 1431649781 us on 32-bit. Every timeout between about 23.9 and 71.6
> > minutes then becomes infinite: usecs_to_jiffies(2000000000) returns
> > MAX_JIFFY_OFFSET instead of 600000. For instance, a PIE tupdate or a
> > DAMOS watermark interval of 30 minutes never fires at HZ=300. With
> > HZ=100, 250 and 1000 the limit is 4294947296, 4294959296 and 4294965296
> > us, so there only the top 19999, 7999 and 1999 values are affected.
> >
> > The check is not needed at all. MAX_JIFFY_OFFSET is at least 2^30 - 2
> > jiffies and HZ is below 12288, so UINT_MAX microseconds are far below
> > MAX_JIFFY_OFFSET jiffies for every supported HZ. Drop it, update the
> > kernel-doc, and add a static_assert() for the assumption.
> >
> > The bogus limit was also hiding a wrap: when HZ divides USEC_PER_SEC,
> > _usecs_to_jiffies() rounds up with (u + USEC_PER_SEC / HZ - 1) in
> > unsigned int, which wraps for u close to UINT_MAX (usecs_to_jiffies(
> > UINT_MAX) would become 0 at HZ=100). Round up with a remainder test
> > instead. The reciprocal multiplication used for the other HZ values is
> > done in 64 bits and does not wrap for any 32-bit input.
>
> Looks like your fixing two things in one patch. I would suggest making
> two patches
>
> >
> > Values above the old limit, including UINT_MAX, now give a finite
> > timeout (at most about 71.6 minutes) with every HZ. No caller uses
> > UINT_MAX as an "infinite" marker; the callers that clamp to UINT_MAX or
> > range-check the result (TCP_RTO_MIN_US, TCP_DELACK_MAX_US) keep working.
> >
> > When HZ does not divide USEC_PER_SEC, jiffies_to_usecs() is out of line,
> > so the check also defeated the constant folding: every
> > usecs_to_jiffies(<constant>) compiled to a call to jiffies_to_usecs()
> > and a compare. Those now fold to a constant.
>
> This commit message (and the others in this series as well as the cover
> letter) is too verbose, hard to read. It is not obvious what you are
> doing and why.
>
> I suggest a table of erroneous values. Like [1]
>
> [1] https://lore.kernel.org/20260923134237.3628505-1-zhanxusheng@xiaomi.com
> >
> > Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> > Assisted-by: LLM
> > Signed-off-by: Shashank Mohan Jain <jain.sm@gmail.com>
> > ---
> > Checked in userspace over all 2^32 inputs, with the current code and
> > the code from this patch both extracted verbatim from the tree and
> > built with the generated timeconst.h, for every Kconfig HZ value (24,
> > 32, 48, 64, 100, 128, 200, 250, 256, 300, 500, 1000, 1024, 1200),
> > 32- and 64-bit. The KUnit test in patch 3 fails without this patch on
> > UML x86_64 (HZ=100) and on qemu x86_64 and i386 with HZ=300, and passes
> > with it. Prepared with Claude Code (Anthropic), model Claude Opus 5.5
> > (claude-opus-5-5).
> >
> >  include/linux/jiffies.h | 22 ++++++++--------------
> >  kernel/time/time.c      | 10 +++++++---
> >  2 files changed, 15 insertions(+), 17 deletions(-)
> >
> > diff --git a/include/linux/jiffies.h b/include/linux/jiffies.h
> > index bbd57061802c..d3f2663e4435 100644
> > --- a/include/linux/jiffies.h
> > +++ b/include/linux/jiffies.h
> > @@ -575,7 +575,8 @@ extern unsigned long __usecs_to_jiffies(const unsigned int u);
> >  #if !(USEC_PER_SEC % HZ)
> >  static inline unsigned long _usecs_to_jiffies(const unsigned int u)
> >  {
> > -     return (u + (USEC_PER_SEC / HZ) - 1) / (USEC_PER_SEC / HZ);
> > +     /* Round up without overflowing for u close to UINT_MAX */
> > +     return u / (USEC_PER_SEC / HZ) + !!(u % (USEC_PER_SEC / HZ));
> >  }
> >  #else
> >  static inline unsigned long _usecs_to_jiffies(const unsigned int u)
> > @@ -589,14 +590,10 @@ static inline unsigned long _usecs_to_jiffies(const unsigned int u)
> >   * usecs_to_jiffies: - convert microseconds to jiffies
> >   * @u:       time in microseconds
> >   *
> > - * conversion is done as follows:
> > - *
> > - * - 'too large' values [that would result in larger than
> > - *   MAX_JIFFY_OFFSET values] mean 'infinite timeout' too.
> > - *
> > - * - all other values are converted to jiffies by either multiplying
> > - *   the input value by a factor or dividing it with a factor and
> > - *   handling any 32-bit overflows as for msecs_to_jiffies.
> > + * conversion is done by either multiplying the input value by a factor
> > + * or dividing it with a factor, rounding up. Any unsigned int number of
> > + * microseconds is far below MAX_JIFFY_OFFSET jiffies for every supported
> > + * HZ, so, unlike msecs_to_jiffies(), there is no 'infinite timeout' case.
> >   *
> >   * usecs_to_jiffies() checks for the passed in value being a constant
> >   * via __builtin_constant_p() allowing gcc to eliminate most of the
> > @@ -611,13 +608,10 @@ static inline unsigned long _usecs_to_jiffies(const unsigned int u)
> >   */
> >  static __always_inline unsigned long usecs_to_jiffies(const unsigned int u)
> >  {
> > -     if (__builtin_constant_p(u)) {
> > -             if (u > jiffies_to_usecs(MAX_JIFFY_OFFSET))
> > -                     return MAX_JIFFY_OFFSET;
> > +     if (__builtin_constant_p(u))
> >               return _usecs_to_jiffies(u);
> > -     } else {
> > +     else
> >               return __usecs_to_jiffies(u);
> > -     }
>
> This effectively becomes
>
> if (__builtin_constant_p(u))
>   return _usecs_to_jiffies(u);
> else
>   return _usecs_to_jiffies(u);
>
> What is the point of this?
>
> Best
> >
> >  extern unsigned long timespec64_to_jiffies(const struct timespec64 *value);
> > diff --git a/kernel/time/time.c b/kernel/time/time.c
> > index 079ab34f61db..470195e88ca0 100644
> > --- a/kernel/time/time.c
> > +++ b/kernel/time/time.c
> > @@ -591,16 +591,20 @@ unsigned long __msecs_to_jiffies(const unsigned int m)
> >  }
> >  EXPORT_SYMBOL(__msecs_to_jiffies);
> >
> > +/*
> > + * UINT_MAX microseconds are far less than MAX_JIFFY_OFFSET jiffies for every
> > + * supported HZ, so usecs_to_jiffies() never needs to saturate.
> > + */
> > +static_assert((u64)UINT_MAX * HZ / USEC_PER_SEC < MAX_JIFFY_OFFSET);
> > +
> >  /**
> >   * __usecs_to_jiffies: - convert microseconds to jiffies
> > - * @u:       time in milliseconds
> > + * @u:       time in microseconds
> >   *
> >   * Return: jiffies value
> >   */
> >  unsigned long __usecs_to_jiffies(const unsigned int u)
> >  {
> > -     if (u > jiffies_to_usecs(MAX_JIFFY_OFFSET))
> > -             return MAX_JIFFY_OFFSET;
> >       return _usecs_to_jiffies(u);
> >  }
> >  EXPORT_SYMBOL(__usecs_to_jiffies);
> > --
> > 2.43.0
> >

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

end of thread, other threads:[~2026-09-28 12:09 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-28  9:35 [PATCH 0/3] time/jiffies: Fix usecs_to_jiffies() saturation and jiffies_to_clock_t() rounding Shashank Mohan Jain
2026-09-28  9:35 ` [PATCH 1/3] time/jiffies: Don't saturate usecs_to_jiffies() on a truncated limit Shashank Mohan Jain
2026-09-28 11:38   ` Joel Granados
2026-09-28 12:09     ` shashank Jain
2026-09-28  9:36 ` [PATCH 2/3] time/jiffies: Use the exact HZ ratio in jiffies_to_clock_t() Shashank Mohan Jain
2026-09-28  9:36 ` [PATCH 3/3] time/kunit: Add tests for usecs_to_jiffies() and jiffies_to_clock_t() Shashank Mohan Jain

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®