From: "Gary Guo" <gary@garyguo.net>
To: "FUJITA Tomonori" <tomo@flapping.org>, <a.hindborg@kernel.org>,
<ojeda@kernel.org>
Cc: <acourbot@nvidia.com>, <aliceryhl@google.com>,
<anna-maria@linutronix.de>, <bjorn3_gh@protonmail.com>,
<boqun@kernel.org>, <dakr@kernel.org>,
<daniel.almeida@collabora.com>, <frederic@kernel.org>,
<gary@garyguo.net>, <jstultz@google.com>, <lossin@kernel.org>,
<lyude@redhat.com>, <sboyd@kernel.org>, <tamird@kernel.org>,
<tglx@kernel.org>, <tmgross@umich.edu>, <work@onurozkan.dev>,
<rust-for-linux@vger.kernel.org>,
"FUJITA Tomonori" <fujita.tomonori@gmail.com>
Subject: Re: [PATCH v3 2/2] rust: time: add Delta::to_jiffies_timeout() for timeout conversion
Date: Wed, 23 Sep 2026 11:22:47 +0100 [thread overview]
Message-ID: <DLMME4OBH3P8.3Q6UK95T2WGI@garyguo.net> (raw)
In-Reply-To: <20260908230445.2430296-3-tomo@flapping.org>
On Wed Sep 9, 2026 at 12:04 AM BST, FUJITA Tomonori wrote:
> From: FUJITA Tomonori <fujita.tomonori@gmail.com>
>
> The boundaries of __msecs_to_jiffies() and nsecs_to_jiffies64() depend
> on which HZ branch is compiled. With CONFIG_HZ_300
> nsecs_to_jiffies64() overflows after 64.99 years, which is less than
> the 292 years a Delta can hold. With HZ=1000 a jiffy is a millisecond,
> so __msecs_to_jiffies() returns its argument unchanged and never caps
> it at MAX_JIFFY_OFFSET.
>
> Compute the conversion in Rust with mul_u64_add_u64_div_u64() instead. It
> returns (a * b + c) / d, computing a * b internally in 128 bits, so
> ceil(nanos * HZ / NSEC_PER_SEC) needs no input clamp and rounds once. The
> bound then follows from the arithmetic: with HZ <= NSEC_PER_SEC, which a
> static_assert() checks, the result is at most the nanosecond count.
>
> Unless the result saturates, the value is rounded up, so the timeout is
> never shorter than the requested span. It saturates at zero jiffies for a
> negative span, i.e. an immediate timeout, and at MAX_JIFFY_OFFSET, the
> upper bound the kernel uses for a jiffies span. Since MAX_JIFFY_OFFSET is
> derived from long, only 32 bit can reach it, and a saturated timeout there
> is finite, so it can be shorter than the requested span.
>
> Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
With a nit below.
> ---
> rust/helpers/helpers.c | 1 +
> rust/helpers/math.c | 8 +++
> rust/kernel/Kconfig.test | 10 ++++
> rust/kernel/time.rs | 105 +++++++++++++++++++++++++++++++++++++++
> 4 files changed, 124 insertions(+)
> create mode 100644 rust/helpers/math.c
>
> diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
> index 440fb7638e3c..08374b163774 100644
> --- a/rust/helpers/helpers.c
> +++ b/rust/helpers/helpers.c
> @@ -73,6 +73,7 @@
> #include "kunit.c"
> #include "list.c"
> #include "maple_tree.c"
> +#include "math.c"
> #include "mm.c"
> #include "mutex.c"
> #include "net/genetlink.c"
> diff --git a/rust/helpers/math.c b/rust/helpers/math.c
> new file mode 100644
> index 000000000000..e2ee29bcce0f
> --- /dev/null
> +++ b/rust/helpers/math.c
> @@ -0,0 +1,8 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <linux/math64.h>
> +
> +__rust_helper u64 rust_helper_mul_u64_add_u64_div_u64(u64 a, u64 b, u64 c, u64 d)
> +{
> + return mul_u64_add_u64_div_u64(a, b, c, d);
> +}
> diff --git a/rust/kernel/Kconfig.test b/rust/kernel/Kconfig.test
> index e6a5c7a795f0..0087749995d2 100644
> --- a/rust/kernel/Kconfig.test
> +++ b/rust/kernel/Kconfig.test
> @@ -83,4 +83,14 @@ config RUST_BITFIELD_KUNIT_TEST
>
> If unsure, say N.
>
> +config RUST_TIME_KUNIT_TEST
> + bool "KUnit tests for the Rust time API" if !KUNIT_ALL_TESTS
> + default KUNIT_ALL_TESTS
> + help
> + This option enables KUnit tests for the Rust time API.
> + These are only for development and testing, not for regular
> + kernel use cases.
> +
> + If unsure, say N.
> +
> endif
> diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs
> index 6c0a5e8090d0..9e66c39f823c 100644
> --- a/rust/kernel/time.rs
> +++ b/rust/kernel/time.rs
> @@ -39,6 +39,10 @@
> /// The number of nanoseconds per second.
> pub const NSEC_PER_SEC: i64 = bindings::NSEC_PER_SEC as i64;
>
> +/// The C side `MAX_JIFFY_OFFSET`, i.e. `((LONG_MAX >> 1) - 1)`. It is the upper
> +/// bound the kernel uses for a jiffies span, not a wait-forever value.
> +const MAX_JIFFY_OFFSET: isize = (isize::MAX >> 1) - 1;
> +
> /// The time unit of Linux kernel. One jiffy equals (1/HZ) second.
> pub type Jiffies = crate::ffi::c_ulong;
>
> @@ -554,6 +558,62 @@ pub fn as_millis_ceil(self) -> i64 {
> }
> }
>
> + /// Convert this span to a [`Delta<Jiffy>`] suitable for use as a timeout.
> + ///
> + /// Unless the result saturates, the value is rounded up to the next whole
> + /// jiffy, so the resulting timeout is never shorter than `self`.
> + ///
> + /// A negative span saturates at zero jiffies, i.e. an immediate timeout.
> + ///
> + /// A span that does not fit saturates at the kernel's [`MAX_JIFFY_OFFSET`],
> + /// the upper bound for a jiffies span. That is a finite timeout, so a
> + /// saturated result can be shorter than the requested span. It is derived
> + /// from `long`, so only 32 bit can reach it, at about 12 days with `HZ=1000`.
> + ///
> + /// # Examples
> + ///
> + /// ```
> + /// use kernel::time::Delta;
> + ///
> + /// // A negative span is an immediate timeout.
> + /// assert_eq!(Delta::from_millis(-1).to_jiffies_timeout().as_jiffies(), 0);
> + ///
> + /// // A span shorter than a jiffy still waits, i.e. the timeout is never
> + /// // shorter than the span.
> + /// assert!(Delta::from_nanos(1).to_jiffies_timeout().as_jiffies() >= 1);
> + /// ```
> + ///
> + /// [`MAX_JIFFY_OFFSET`]: srctree/include/linux/jiffies.h
> + #[inline]
> + pub fn to_jiffies_timeout(self) -> Delta<Jiffy> {
> + const HZ: u64 = bindings::HZ as u64;
> +
> + // The quotient `(nsecs * HZ + NSEC_PER_SEC - 1) / NSEC_PER_SEC` has to fit in
> + // `u64`; `nsecs * HZ` does not. With `HZ <= NSEC_PER_SEC` the numerator is at
> + // most `(nsecs + 1) * NSEC_PER_SEC - 1`, so the quotient is at most `nsecs`.
> + crate::static_assert!(HZ <= NSEC_PER_SEC as u64);
I'm pretty sure we'll never want to a tick per ns, but hey maybe that'll not be
true for a future 5000000 GHz processor :)
> +
> + // CAST: `max()` makes the value non-negative, so the cast keeps it.
> + let nsecs = self.as_nanos().max(0) as u64;
Please use fn syntax and have `core::cmp::max(self.as_nanos(), 0)` or
`u64::max(..)`. Due to the method syntax hinting some action is taking place,
many people find it confusing, because you can easily understand `.max` it as
"clamp to a max of" while it actually means "clamp to a min of".
See https://internals.rust-lang.org/t/am-i-the-only-one-confused-by-a-min-b-and-a-max-b/13252
I'm somewhat surprised that Clippy doesn't have a restriction lint for this,
though.
Best,
Gary
> +
> + // SAFETY: `mul_u64_add_u64_div_u64()` must not be called with a zero divisor,
> + // and its result must fit in `u64`. `NSEC_PER_SEC` is a non-zero constant, and
> + // the assertion above bounds the quotient by `nsecs`.
> + let jiffies = unsafe {
> + bindings::mul_u64_add_u64_div_u64(
> + nsecs,
> + HZ,
> + (NSEC_PER_SEC - 1) as u64,
> + NSEC_PER_SEC as u64,
> + )
> + };
> +
> + // CAST: `jiffies` is clamped to `MAX_JIFFY_OFFSET`, which is `<= isize::MAX`.
> + let jiffies = jiffies.min(MAX_JIFFY_OFFSET as u64) as isize;
> +
> + Delta::<Jiffy>::from_jiffies(jiffies)
> + }
> +
> /// Return `self % dividend` where `dividend` is in nanoseconds.
> ///
> /// The kernel doesn't have any emulation for `s64 % s64` on 32 bit platforms, so this is
next prev parent reply other threads:[~2026-09-23 10:22 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <ecZSyn5iOGZxLQdeadMYD0WYwv1yJFnIc0PLl8ULc8k4LJrT2wwo1dn9jSFXDORi20jKY0IpvWoVxrNZsSaOnQ==@protonmail.internalid>
2026-09-08 23:04 ` [PATCH v3 0/2] Add Delta::to_jiffies_timeout() FUJITA Tomonori
2026-09-08 23:04 ` [PATCH v3 1/2] rust: bindings: set HZ to CONFIG_HZ FUJITA Tomonori
2026-09-23 10:22 ` Gary Guo
2026-09-08 23:04 ` [PATCH v3 2/2] rust: time: add Delta::to_jiffies_timeout() for timeout conversion FUJITA Tomonori
2026-09-23 10:22 ` Gary Guo [this message]
2026-09-24 23:33 ` FUJITA Tomonori
2026-09-23 8:22 ` [PATCH v3 0/2] Add Delta::to_jiffies_timeout() Andreas Hindborg
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DLMME4OBH3P8.3Q6UK95T2WGI@garyguo.net \
--to=gary@garyguo.net \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=anna-maria@linutronix.de \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=frederic@kernel.org \
--cc=fujita.tomonori@gmail.com \
--cc=jstultz@google.com \
--cc=lossin@kernel.org \
--cc=lyude@redhat.com \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=sboyd@kernel.org \
--cc=tamird@kernel.org \
--cc=tglx@kernel.org \
--cc=tmgross@umich.edu \
--cc=tomo@flapping.org \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox