* [PATCH v1 0/2] Add Delta::to_jiffies_timeout() with tests
@ 2026-08-11 15:01 FUJITA Tomonori
2026-08-11 15:01 ` [PATCH v1 1/2] rust: time: add example and KUnit test for Delta::as_millis_ceil() FUJITA Tomonori
2026-08-11 15:01 ` [PATCH v1 2/2] rust: time: add Delta::to_jiffies_timeout() for timeout conversion FUJITA Tomonori
0 siblings, 2 replies; 4+ messages in thread
From: FUJITA Tomonori @ 2026-08-11 15:01 UTC (permalink / raw)
To: a.hindborg, ojeda
Cc: acourbot, aliceryhl, anna-maria, bjorn3_gh, boqun, dakr,
daniel.almeida, frederic, gary, jstultz, lossin, lyude, sboyd,
tamird, tglx, tmgross, work, rust-for-linux, FUJITA Tomonori
From: FUJITA Tomonori <fujita.tomonori@gmail.com>
The first patch is new. It adds an example and a KUnit test for
Delta::as_millis_ceil(), which Miguel asked for [1].
The second patch was previously posted as patch 4 of [2]; patches 1-3
of that series have been applied. It has been reworked to fix the bug
Miguel reported [1]: on 32 bit with HZ=1000, to_jiffies_timeout()
could return a value larger than MAX_JIFFY_OFFSET.
Both patches follow the format agreed in [3]: the examples show the
behaviour, while the boundary inputs live in `#[test]` KUnit tests, with the
magic numbers as named constants local to the test module rather than part
of the exported API.
[1] https://lore.kernel.org/rust-for-linux/CANiq72kHP1s7bGr4=xfJGR8KY=aUpjOB4OwLEG5E=TKULXg5Rg@mail.gmail.com/
[2] https://lore.kernel.org/rust-for-linux/20260808062839.1159990-1-tomo@flapping.org/
[3] https://lore.kernel.org/rust-for-linux/874ihxgxy0.fsf@kernel.org/
FUJITA Tomonori (2):
rust: time: add example and KUnit test for Delta::as_millis_ceil()
rust: time: add Delta::to_jiffies_timeout() for timeout conversion
rust/kernel/Kconfig.test | 10 ++++
rust/kernel/time.rs | 108 +++++++++++++++++++++++++++++++++++++++
2 files changed, 118 insertions(+)
base-commit: 8fe5e5f62bdb9660999449a4b5eaebcc37d7f842
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v1 1/2] rust: time: add example and KUnit test for Delta::as_millis_ceil()
2026-08-11 15:01 [PATCH v1 0/2] Add Delta::to_jiffies_timeout() with tests FUJITA Tomonori
@ 2026-08-11 15:01 ` FUJITA Tomonori
2026-08-11 15:01 ` [PATCH v1 2/2] rust: time: add Delta::to_jiffies_timeout() for timeout conversion FUJITA Tomonori
1 sibling, 0 replies; 4+ messages in thread
From: FUJITA Tomonori @ 2026-08-11 15:01 UTC (permalink / raw)
To: a.hindborg, ojeda
Cc: acourbot, aliceryhl, anna-maria, bjorn3_gh, boqun, dakr,
daniel.almeida, frederic, gary, jstultz, lossin, lyude, sboyd,
tamird, tglx, tmgross, work, rust-for-linux, FUJITA Tomonori
From: FUJITA Tomonori <fujita.tomonori@gmail.com>
Add an example showing that the value is rounded towards positive
infinity, for both positive and negative spans.
as_millis_ceil() uses the same idiom as as_micros_ceil(), which dropped
the rounding bias near i64::MAX before commit ec90dfcf05f0 ("rust: time:
fix as_micros_ceil() rounding near i64::MAX"), so add a KUnit test for
the i64::MAX and i64::MIN extremes.
Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
---
rust/kernel/Kconfig.test | 10 ++++++++++
rust/kernel/time.rs | 42 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 52 insertions(+)
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..b0b43ad3aa47 100644
--- a/rust/kernel/time.rs
+++ b/rust/kernel/time.rs
@@ -530,6 +530,21 @@ pub fn as_millis(self) -> i64 {
/// Return the smallest number of milliseconds greater than or equal
/// to the value in the [`Delta`].
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::time::Delta;
+ ///
+ /// // Whole milliseconds are returned as-is.
+ /// assert_eq!(Delta::from_millis(2).as_millis_ceil(), 2);
+ /// assert_eq!(Delta::from_nanos(1_000_000).as_millis_ceil(), 1);
+ /// assert_eq!(Delta::from_nanos(-1_000_000).as_millis_ceil(), -1);
+ ///
+ /// // Anything else is rounded towards positive infinity.
+ /// assert_eq!(Delta::from_nanos(1_000_001).as_millis_ceil(), 2);
+ /// assert_eq!(Delta::from_nanos(-1_000_001).as_millis_ceil(), -1);
+ /// ```
#[inline]
pub fn as_millis_ceil(self) -> i64 {
// Only positive values need to be rounded up: truncating division already
@@ -580,3 +595,30 @@ pub fn rem_nanos(self, dividend: i32) -> Self {
}
}
}
+
+#[cfg(CONFIG_RUST_TIME_KUNIT_TEST)]
+#[macros::kunit_tests(rust_kernel_time)]
+mod tests {
+ use super::*;
+
+ /// `i64::MAX` nanoseconds in milliseconds, rounded towards positive infinity.
+ const MAX_MILLIS_CEIL: i64 = 9_223_372_036_855;
+
+ /// `i64::MIN` nanoseconds in milliseconds, rounded towards positive infinity.
+ const MIN_MILLIS_CEIL: i64 = -9_223_372_036_854;
+
+ #[test]
+ fn as_millis_ceil_extremes() {
+ // The rounding bias must survive near `i64::MAX`.
+ assert_eq!(
+ Delta::from_nanos(i64::MAX).as_millis_ceil(),
+ MAX_MILLIS_CEIL
+ );
+
+ // No bias is applied to negative values, so `i64::MIN` cannot overflow.
+ assert_eq!(
+ Delta::from_nanos(i64::MIN).as_millis_ceil(),
+ MIN_MILLIS_CEIL
+ );
+ }
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v1 2/2] rust: time: add Delta::to_jiffies_timeout() for timeout conversion
2026-08-11 15:01 [PATCH v1 0/2] Add Delta::to_jiffies_timeout() with tests FUJITA Tomonori
2026-08-11 15:01 ` [PATCH v1 1/2] rust: time: add example and KUnit test for Delta::as_millis_ceil() FUJITA Tomonori
@ 2026-08-11 15:01 ` FUJITA Tomonori
2026-08-11 16:44 ` Gary Guo
1 sibling, 1 reply; 4+ messages in thread
From: FUJITA Tomonori @ 2026-08-11 15:01 UTC (permalink / raw)
To: a.hindborg, ojeda
Cc: acourbot, aliceryhl, anna-maria, bjorn3_gh, boqun, dakr,
daniel.almeida, frederic, gary, jstultz, lossin, lyude, sboyd,
tamird, tglx, tmgross, work, rust-for-linux, FUJITA Tomonori
From: FUJITA Tomonori <fujita.tomonori@gmail.com>
Add Delta<Nsec>::to_jiffies_timeout() conversion. Unless the result
saturates, the value is rounded up, so the resulting timeout is never
shorter than the requested span.
The result saturates at zero jiffies for a negative span, i.e. an
immediate timeout, and at the kernel's MAX_JIFFY_OFFSET "wait forever"
value for a span that is too large.
Reviewed-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Andreas Hindborg <a.hindborg@kernel.org>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
---
rust/kernel/time.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 66 insertions(+)
diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs
index b0b43ad3aa47..4ec1748c2602 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)`, which the kernel
+/// treats as an infinite timeout.
+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;
@@ -569,6 +573,51 @@ 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` (as
+ /// [`msecs_to_jiffies()`] does).
+ ///
+ /// The result saturates at zero jiffies for a negative span, i.e. an
+ /// immediate timeout, and at the kernel's [`MAX_JIFFY_OFFSET`] for a span
+ /// that is too large, which the C side treats as an infinite timeout.
+ ///
+ /// # 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);
+ /// ```
+ ///
+ /// [`msecs_to_jiffies()`]: srctree/include/linux/jiffies.h
+ /// [`MAX_JIFFY_OFFSET`]: srctree/include/linux/jiffies.h
+ #[inline]
+ pub fn to_jiffies_timeout(self) -> Delta<Jiffy> {
+ let msecs = self.as_millis_ceil();
+
+ // CAST: `msecs` is clamped to `0..=c_uint::MAX`, so it is non-negative and
+ // fits in `c_uint`.
+ let msecs = msecs.clamp(0, i64::from(crate::ffi::c_uint::MAX)) as crate::ffi::c_uint;
+
+ // SAFETY: `__msecs_to_jiffies()` is always safe to call.
+ let jiffies = unsafe { bindings::__msecs_to_jiffies(msecs) };
+
+ // `__msecs_to_jiffies()` only saturates when its argument is negative as an
+ // `int`: with `HZ=1000` it returns `msecs` as-is, which exceeds
+ // `MAX_JIFFY_OFFSET` on 32 bit.
+ let jiffies = jiffies.min(MAX_JIFFY_OFFSET as crate::ffi::c_ulong);
+
+ // CAST: `jiffies` is clamped to `MAX_JIFFY_OFFSET`, which is `<= isize::MAX`.
+ Delta::<Jiffy>::from_jiffies(jiffies as isize)
+ }
+
/// 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
@@ -621,4 +670,21 @@ fn as_millis_ceil_extremes() {
MIN_MILLIS_CEIL
);
}
+
+ #[test]
+ fn to_jiffies_timeout_saturates() {
+ // `__msecs_to_jiffies()` does not clamp its result for every `HZ` configuration,
+ // e.g. with `HZ=1000` it returns the millisecond value as-is, which exceeds
+ // `MAX_JIFFY_OFFSET` on 32 bit.
+ let max = Delta::from_millis(i64::from(i32::MAX)).to_jiffies_timeout();
+ assert!(max.as_jiffies() <= MAX_JIFFY_OFFSET);
+
+ // An overlong span is an infinite timeout.
+ let overlong = Delta::from_nanos(i64::MAX).to_jiffies_timeout();
+ assert_eq!(overlong.as_jiffies(), MAX_JIFFY_OFFSET);
+
+ // A negative span is an immediate timeout, however long it is.
+ let negative = Delta::from_nanos(i64::MIN).to_jiffies_timeout();
+ assert_eq!(negative.as_jiffies(), 0);
+ }
}
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v1 2/2] rust: time: add Delta::to_jiffies_timeout() for timeout conversion
2026-08-11 15:01 ` [PATCH v1 2/2] rust: time: add Delta::to_jiffies_timeout() for timeout conversion FUJITA Tomonori
@ 2026-08-11 16:44 ` Gary Guo
0 siblings, 0 replies; 4+ messages in thread
From: Gary Guo @ 2026-08-11 16:44 UTC (permalink / raw)
To: FUJITA Tomonori, a.hindborg, ojeda
Cc: acourbot, aliceryhl, anna-maria, bjorn3_gh, boqun, dakr,
daniel.almeida, frederic, gary, jstultz, lossin, lyude, sboyd,
tamird, tglx, tmgross, work, rust-for-linux, FUJITA Tomonori
On Tue Aug 11, 2026 at 4:01 PM BST, FUJITA Tomonori wrote:
> From: FUJITA Tomonori <fujita.tomonori@gmail.com>
>
> Add Delta<Nsec>::to_jiffies_timeout() conversion. Unless the result
> saturates, the value is rounded up, so the resulting timeout is never
> shorter than the requested span.
>
> The result saturates at zero jiffies for a negative span, i.e. an
> immediate timeout, and at the kernel's MAX_JIFFY_OFFSET "wait forever"
> value for a span that is too large.
>
> Reviewed-by: Gary Guo <gary@garyguo.net>
You should drop old review tags given this has been changed non trivially.
> Reviewed-by: Andreas Hindborg <a.hindborg@kernel.org>
> Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
> ---
> rust/kernel/time.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 66 insertions(+)
>
> diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs
> index b0b43ad3aa47..4ec1748c2602 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)`, which the kernel
> +/// treats as an infinite timeout.
> +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;
>
> @@ -569,6 +573,51 @@ 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` (as
> + /// [`msecs_to_jiffies()`] does).
> + ///
> + /// The result saturates at zero jiffies for a negative span, i.e. an
> + /// immediate timeout, and at the kernel's [`MAX_JIFFY_OFFSET`] for a span
> + /// that is too large, which the C side treats as an infinite timeout.
> + ///
> + /// # 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);
> + /// ```
> + ///
> + /// [`msecs_to_jiffies()`]: srctree/include/linux/jiffies.h
> + /// [`MAX_JIFFY_OFFSET`]: srctree/include/linux/jiffies.h
> + #[inline]
> + pub fn to_jiffies_timeout(self) -> Delta<Jiffy> {
> + let msecs = self.as_millis_ceil();
> +
> + // CAST: `msecs` is clamped to `0..=c_uint::MAX`, so it is non-negative and
> + // fits in `c_uint`.
> + let msecs = msecs.clamp(0, i64::from(crate::ffi::c_uint::MAX)) as crate::ffi::c_uint;
> +
> + // SAFETY: `__msecs_to_jiffies()` is always safe to call.
> + let jiffies = unsafe { bindings::__msecs_to_jiffies(msecs) };
As I mentioned in previous 2 versions, I don't think __msecs_to_jiffies should
be used for this. You're doing two rounding and saturation operations here.
If you do nsecs_to_jiffies64 and then clamp, you wouldn't run into any of these
boundary conditions.
Best,
Gary
> +
> + // `__msecs_to_jiffies()` only saturates when its argument is negative as an
> + // `int`: with `HZ=1000` it returns `msecs` as-is, which exceeds
> + // `MAX_JIFFY_OFFSET` on 32 bit.
> + let jiffies = jiffies.min(MAX_JIFFY_OFFSET as crate::ffi::c_ulong);
> +
> + // CAST: `jiffies` is clamped to `MAX_JIFFY_OFFSET`, which is `<= isize::MAX`.
> + Delta::<Jiffy>::from_jiffies(jiffies as isize)
> + }
> +
> /// 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
> @@ -621,4 +670,21 @@ fn as_millis_ceil_extremes() {
> MIN_MILLIS_CEIL
> );
> }
> +
> + #[test]
> + fn to_jiffies_timeout_saturates() {
> + // `__msecs_to_jiffies()` does not clamp its result for every `HZ` configuration,
> + // e.g. with `HZ=1000` it returns the millisecond value as-is, which exceeds
> + // `MAX_JIFFY_OFFSET` on 32 bit.
> + let max = Delta::from_millis(i64::from(i32::MAX)).to_jiffies_timeout();
> + assert!(max.as_jiffies() <= MAX_JIFFY_OFFSET);
> +
> + // An overlong span is an infinite timeout.
> + let overlong = Delta::from_nanos(i64::MAX).to_jiffies_timeout();
> + assert_eq!(overlong.as_jiffies(), MAX_JIFFY_OFFSET);
> +
> + // A negative span is an immediate timeout, however long it is.
> + let negative = Delta::from_nanos(i64::MIN).to_jiffies_timeout();
> + assert_eq!(negative.as_jiffies(), 0);
> + }
> }
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-08-11 16:44 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-11 15:01 [PATCH v1 0/2] Add Delta::to_jiffies_timeout() with tests FUJITA Tomonori
2026-08-11 15:01 ` [PATCH v1 1/2] rust: time: add example and KUnit test for Delta::as_millis_ceil() FUJITA Tomonori
2026-08-11 15:01 ` [PATCH v1 2/2] rust: time: add Delta::to_jiffies_timeout() for timeout conversion FUJITA Tomonori
2026-08-11 16:44 ` Gary Guo
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.