Rust for Linux List
 help / color / mirror / Atom feed
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


  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