rust-for-linux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v1 0/2] Add read_poll_timeout_atomic support
@ 2025-08-21  3:57 FUJITA Tomonori
  2025-08-21  3:57 ` [PATCH v1 1/2] rust: add udelay() function FUJITA Tomonori
  2025-08-21  3:57 ` [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function FUJITA Tomonori
  0 siblings, 2 replies; 22+ messages in thread
From: FUJITA Tomonori @ 2025-08-21  3:57 UTC (permalink / raw)
  To: a.hindborg, alex.gaynor, ojeda
  Cc: aliceryhl, anna-maria, bjorn3_gh, boqun.feng, dakr, frederic,
	gary, jstultz, linux-kernel, lossin, lyude, rust-for-linux, sboyd,
	tglx, tmgross, acourbot, daniel.almeida

Add a helper function, read_poll_timeout_atomic() to poll periodically
until a condition is met or a timeout is reached.

Unlike read_poll_timeout(), read_poll_timeout_atomic() performs
busy-wait so it can be used in an atomic context.

This patchset can be applied on top of the read_poll_timeout patchset (v3) [1].

[1] https://lore.kernel.org/lkml/20250821002055.3654160-1-fujita.tomonori@gmail.com/

FUJITA Tomonori (2):
  rust: add udelay() function
  rust: Add read_poll_timeout_atomic function

 rust/helpers/time.c       |  5 +++
 rust/kernel/io/poll.rs    | 90 ++++++++++++++++++++++++++++++++++++++-
 rust/kernel/time/delay.rs | 34 +++++++++++++++
 3 files changed, 128 insertions(+), 1 deletion(-)


base-commit: a3b971f57db41ef1c68f842cd3c2c00b3df54ce4
-- 
2.43.0


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

* [PATCH v1 1/2] rust: add udelay() function
  2025-08-21  3:57 [PATCH v1 0/2] Add read_poll_timeout_atomic support FUJITA Tomonori
@ 2025-08-21  3:57 ` FUJITA Tomonori
  2025-08-26  9:09   ` Andreas Hindborg
  2025-08-26 12:44   ` Daniel Almeida
  2025-08-21  3:57 ` [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function FUJITA Tomonori
  1 sibling, 2 replies; 22+ messages in thread
From: FUJITA Tomonori @ 2025-08-21  3:57 UTC (permalink / raw)
  To: a.hindborg, alex.gaynor, ojeda
  Cc: aliceryhl, anna-maria, bjorn3_gh, boqun.feng, dakr, frederic,
	gary, jstultz, linux-kernel, lossin, lyude, rust-for-linux, sboyd,
	tglx, tmgross, acourbot, daniel.almeida

Add udelay() function, inserts a delay based on microseconds with busy
waiting, in preparation for supporting read_poll_timeout_atomic().

Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
---
 rust/helpers/time.c       |  5 +++++
 rust/kernel/time/delay.rs | 34 ++++++++++++++++++++++++++++++++++
 2 files changed, 39 insertions(+)

diff --git a/rust/helpers/time.c b/rust/helpers/time.c
index a318e9fa4408..67a36ccc3ec4 100644
--- a/rust/helpers/time.c
+++ b/rust/helpers/time.c
@@ -33,3 +33,8 @@ s64 rust_helper_ktime_to_ms(const ktime_t kt)
 {
 	return ktime_to_ms(kt);
 }
+
+void rust_helper_udelay(unsigned long usec)
+{
+	udelay(usec);
+}
diff --git a/rust/kernel/time/delay.rs b/rust/kernel/time/delay.rs
index eb8838da62bc..baae3238d419 100644
--- a/rust/kernel/time/delay.rs
+++ b/rust/kernel/time/delay.rs
@@ -47,3 +47,37 @@ pub fn fsleep(delta: Delta) {
         bindings::fsleep(delta.as_micros_ceil() as c_ulong)
     }
 }
+
+/// Inserts a delay based on microseconds with busy waiting.
+///
+/// Equivalent to the C side [`udelay()`], which delays in microseconds.
+///
+/// `delta` must be within `[0, `MAX_UDELAY_MS`]` in milliseconds;
+/// otherwise, it is erroneous behavior. That is, it is considered a bug to
+/// call this function with an out-of-range value, in which case the function
+/// will insert a delay for at least the maximum value in the range and
+/// may warn in the future.
+///
+/// The behavior above differs from the C side [`udelay()`] for which out-of-range
+/// values could lead to an overflow and unexpected behavior.
+///
+/// [`udelay()`]: https://docs.kernel.org/timers/delay_sleep_functions.html#c.udelay
+pub fn udelay(delta: Delta) {
+    const MAX_UDELAY_DELTA: Delta = Delta::from_millis(bindings::MAX_UDELAY_MS as i64);
+
+    let delta = if (Delta::ZERO..=MAX_UDELAY_DELTA).contains(&delta) {
+        delta
+    } else {
+        // TODO: Add WARN_ONCE() when it's supported.
+        MAX_UDELAY_DELTA
+    };
+
+    // SAFETY: It is always safe to call `udelay()` with any duration.
+    unsafe {
+        // Convert the duration to microseconds and round up to preserve
+        // the guarantee; `udelay()` inserts a delay for at least
+        // the provided duration, but that it may delay for longer
+        // under some circumstances.
+        bindings::udelay(delta.as_micros_ceil() as c_ulong)
+    }
+}
-- 
2.43.0


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

* [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-21  3:57 [PATCH v1 0/2] Add read_poll_timeout_atomic support FUJITA Tomonori
  2025-08-21  3:57 ` [PATCH v1 1/2] rust: add udelay() function FUJITA Tomonori
@ 2025-08-21  3:57 ` FUJITA Tomonori
  2025-08-26 14:02   ` Daniel Almeida
  2025-08-26 14:12   ` Danilo Krummrich
  1 sibling, 2 replies; 22+ messages in thread
From: FUJITA Tomonori @ 2025-08-21  3:57 UTC (permalink / raw)
  To: a.hindborg, alex.gaynor, ojeda
  Cc: aliceryhl, anna-maria, bjorn3_gh, boqun.feng, dakr, frederic,
	gary, jstultz, linux-kernel, lossin, lyude, rust-for-linux, sboyd,
	tglx, tmgross, acourbot, daniel.almeida

Add read_poll_timeout_atomic function which polls periodically until a
condition is met, an error occurs, or the timeout is reached.

The C's read_poll_timeout_atomic (include/linux/iopoll.h) is a
complicated macro and a simple wrapper for Rust doesn't work. So this
implements the same functionality in Rust.

The delay_before_read argument isn't supported since there is no user
for now. It's rarely used in the C version.

Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
---
 rust/kernel/io/poll.rs | 90 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 89 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/io/poll.rs b/rust/kernel/io/poll.rs
index 7af1934e397a..71c2c0e0d8b4 100644
--- a/rust/kernel/io/poll.rs
+++ b/rust/kernel/io/poll.rs
@@ -8,7 +8,10 @@
     error::{code::*, Result},
     processor::cpu_relax,
     task::might_sleep,
-    time::{delay::fsleep, Delta, Instant, Monotonic},
+    time::{
+        delay::{fsleep, udelay},
+        Delta, Instant, Monotonic,
+    },
 };
 
 /// Polls periodically until a condition is met, an error occurs,
@@ -102,3 +105,88 @@ pub fn read_poll_timeout<Op, Cond, T>(
         cpu_relax();
     }
 }
+
+/// Polls periodically until a condition is met, an error occurs,
+/// or the timeout is reached.
+///
+/// The function repeatedly executes the given operation `op` closure and
+/// checks its result using the condition closure `cond`.
+///
+/// If `cond` returns `true`, the function returns successfully with the result of `op`.
+/// Otherwise, it performs a busy wait for a duration specified by `delay_delta`
+/// before executing `op` again.
+///
+/// This process continues until either `op` returns an error, `cond`
+/// returns `true`, or the timeout specified by `timeout_delta` is
+/// reached.
+///
+/// # Errors
+///
+/// If `op` returns an error, then that error is returned directly.
+///
+/// If the timeout specified by `timeout_delta` is reached, then
+/// `Err(ETIMEDOUT)` is returned.
+///
+/// # Examples
+///
+/// ```no_run
+/// use kernel::io::{Io, poll::read_poll_timeout_atomic};
+/// use kernel::time::Delta;
+///
+/// const HW_READY: u16 = 0x01;
+///
+/// fn wait_for_hardware<const SIZE: usize>(io: &Io<SIZE>) -> Result<()> {
+///     match read_poll_timeout_atomic(
+///         // The `op` closure reads the value of a specific status register.
+///         || io.try_read16(0x1000),
+///         // The `cond` closure takes a reference to the value returned by `op`
+///         // and checks whether the hardware is ready.
+///         |val: &u16| *val == HW_READY,
+///         Delta::from_micros(50),
+///         Delta::from_micros(300),
+///     ) {
+///         Ok(_) => {
+///             // The hardware is ready. The returned value of the `op` closure
+///             // isn't used.
+///             Ok(())
+///         }
+///         Err(e) => Err(e),
+///     }
+/// }
+/// ```
+pub fn read_poll_timeout_atomic<Op, Cond, T>(
+    mut op: Op,
+    mut cond: Cond,
+    delay_delta: Delta,
+    timeout_delta: Delta,
+) -> Result<T>
+where
+    Op: FnMut() -> Result<T>,
+    Cond: FnMut(&T) -> bool,
+{
+    let mut left_ns = timeout_delta.as_nanos();
+    let delay_ns = delay_delta.as_nanos();
+
+    loop {
+        let val = op()?;
+        if cond(&val) {
+            // Unlike the C version, we immediately return.
+            // We know the condition is met so we don't need to check again.
+            return Ok(val);
+        }
+
+        if left_ns < 0 {
+            // Unlike the C version, we immediately return.
+            // We have just called `op()` so we don't need to call it again.
+            return Err(ETIMEDOUT);
+        }
+
+        if !delay_delta.is_zero() {
+            udelay(delay_delta);
+            left_ns -= delay_ns;
+        }
+
+        cpu_relax();
+        left_ns -= 1;
+    }
+}
-- 
2.43.0


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

* Re: [PATCH v1 1/2] rust: add udelay() function
  2025-08-21  3:57 ` [PATCH v1 1/2] rust: add udelay() function FUJITA Tomonori
@ 2025-08-26  9:09   ` Andreas Hindborg
  2025-08-26 11:59     ` FUJITA Tomonori
  2025-08-26 12:44   ` Daniel Almeida
  1 sibling, 1 reply; 22+ messages in thread
From: Andreas Hindborg @ 2025-08-26  9:09 UTC (permalink / raw)
  To: FUJITA Tomonori, alex.gaynor, ojeda
  Cc: aliceryhl, anna-maria, bjorn3_gh, boqun.feng, dakr, frederic,
	gary, jstultz, linux-kernel, lossin, lyude, rust-for-linux, sboyd,
	tglx, tmgross, acourbot, daniel.almeida

"FUJITA Tomonori" <fujita.tomonori@gmail.com> writes:

> Add udelay() function, inserts a delay based on microseconds with busy
> waiting, in preparation for supporting read_poll_timeout_atomic().
>
> Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
> ---
>  rust/helpers/time.c       |  5 +++++
>  rust/kernel/time/delay.rs | 34 ++++++++++++++++++++++++++++++++++
>  2 files changed, 39 insertions(+)
>
> diff --git a/rust/helpers/time.c b/rust/helpers/time.c
> index a318e9fa4408..67a36ccc3ec4 100644
> --- a/rust/helpers/time.c
> +++ b/rust/helpers/time.c
> @@ -33,3 +33,8 @@ s64 rust_helper_ktime_to_ms(const ktime_t kt)
>  {
>  	return ktime_to_ms(kt);
>  }
> +
> +void rust_helper_udelay(unsigned long usec)
> +{
> +	udelay(usec);
> +}
> diff --git a/rust/kernel/time/delay.rs b/rust/kernel/time/delay.rs
> index eb8838da62bc..baae3238d419 100644
> --- a/rust/kernel/time/delay.rs
> +++ b/rust/kernel/time/delay.rs
> @@ -47,3 +47,37 @@ pub fn fsleep(delta: Delta) {
>          bindings::fsleep(delta.as_micros_ceil() as c_ulong)
>      }
>  }
> +
> +/// Inserts a delay based on microseconds with busy waiting.
> +///
> +/// Equivalent to the C side [`udelay()`], which delays in microseconds.
> +///
> +/// `delta` must be within `[0, `MAX_UDELAY_MS`]` in milliseconds;
> +/// otherwise, it is erroneous behavior. That is, it is considered a bug to
> +/// call this function with an out-of-range value, in which case the function
> +/// will insert a delay for at least the maximum value in the range and
> +/// may warn in the future.
> +///
> +/// The behavior above differs from the C side [`udelay()`] for which out-of-range
> +/// values could lead to an overflow and unexpected behavior.
> +///
> +/// [`udelay()`]: https://docs.kernel.org/timers/delay_sleep_functions.html#c.udelay
> +pub fn udelay(delta: Delta) {
> +    const MAX_UDELAY_DELTA: Delta = Delta::from_millis(bindings::MAX_UDELAY_MS as i64);
> +
> +    let delta = if (Delta::ZERO..=MAX_UDELAY_DELTA).contains(&delta) {
> +        delta
> +    } else {
> +        // TODO: Add WARN_ONCE() when it's supported.
> +        MAX_UDELAY_DELTA
> +    };
> +
> +    // SAFETY: It is always safe to call `udelay()` with any duration.

Function documentation says it is overflow and unexpected behavior to
call `udelay` with out of range value, but above comment says any
duration is safe. Which is it?


Best regards,
Andreas Hindborg




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

* Re: [PATCH v1 1/2] rust: add udelay() function
  2025-08-26  9:09   ` Andreas Hindborg
@ 2025-08-26 11:59     ` FUJITA Tomonori
  2025-08-26 18:03       ` Miguel Ojeda
  0 siblings, 1 reply; 22+ messages in thread
From: FUJITA Tomonori @ 2025-08-26 11:59 UTC (permalink / raw)
  To: a.hindborg
  Cc: fujita.tomonori, alex.gaynor, ojeda, aliceryhl, anna-maria,
	bjorn3_gh, boqun.feng, dakr, frederic, gary, jstultz,
	linux-kernel, lossin, lyude, rust-for-linux, sboyd, tglx, tmgross,
	acourbot, daniel.almeida

On Tue, 26 Aug 2025 11:09:12 +0200
Andreas Hindborg <a.hindborg@kernel.org> wrote:

>> +/// Inserts a delay based on microseconds with busy waiting.
>> +///
>> +/// Equivalent to the C side [`udelay()`], which delays in microseconds.
>> +///
>> +/// `delta` must be within `[0, `MAX_UDELAY_MS`]` in milliseconds;
>> +/// otherwise, it is erroneous behavior. That is, it is considered a bug to
>> +/// call this function with an out-of-range value, in which case the function
>> +/// will insert a delay for at least the maximum value in the range and
>> +/// may warn in the future.
>> +///
>> +/// The behavior above differs from the C side [`udelay()`] for which out-of-range
>> +/// values could lead to an overflow and unexpected behavior.
>> +///
>> +/// [`udelay()`]: https://docs.kernel.org/timers/delay_sleep_functions.html#c.udelay
>> +pub fn udelay(delta: Delta) {
>> +    const MAX_UDELAY_DELTA: Delta = Delta::from_millis(bindings::MAX_UDELAY_MS as i64);
>> +
>> +    let delta = if (Delta::ZERO..=MAX_UDELAY_DELTA).contains(&delta) {
>> +        delta
>> +    } else {
>> +        // TODO: Add WARN_ONCE() when it's supported.
>> +        MAX_UDELAY_DELTA
>> +    };
>> +
>> +    // SAFETY: It is always safe to call `udelay()` with any duration.
> 
> Function documentation says it is overflow and unexpected behavior to
> call `udelay` with out of range value, but above comment says any
> duration is safe. Which is it?

This can lead to an unexpected delay duration, but it's safe in Rust’s
sense of safety?

If not, how about the followings?

// SAFETY: `delta` is clamped to the range [0, MAX_UDELAY_DELTA],
// so calling `udelay()` with it is always safe.

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

* Re: [PATCH v1 1/2] rust: add udelay() function
  2025-08-21  3:57 ` [PATCH v1 1/2] rust: add udelay() function FUJITA Tomonori
  2025-08-26  9:09   ` Andreas Hindborg
@ 2025-08-26 12:44   ` Daniel Almeida
  2025-08-27  2:43     ` FUJITA Tomonori
  1 sibling, 1 reply; 22+ messages in thread
From: Daniel Almeida @ 2025-08-26 12:44 UTC (permalink / raw)
  To: FUJITA Tomonori
  Cc: a.hindborg, alex.gaynor, ojeda, aliceryhl, anna-maria, bjorn3_gh,
	boqun.feng, dakr, frederic, gary, jstultz, linux-kernel, lossin,
	lyude, rust-for-linux, sboyd, tglx, tmgross, acourbot

Hi Fujita,

> On 21 Aug 2025, at 00:57, FUJITA Tomonori <fujita.tomonori@gmail.com> wrote:
> 
> Add udelay() function, inserts a delay based on microseconds with busy
> waiting, in preparation for supporting read_poll_timeout_atomic().
> 
> Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
> ---
> rust/helpers/time.c       |  5 +++++
> rust/kernel/time/delay.rs | 34 ++++++++++++++++++++++++++++++++++
> 2 files changed, 39 insertions(+)
> 
> diff --git a/rust/helpers/time.c b/rust/helpers/time.c
> index a318e9fa4408..67a36ccc3ec4 100644
> --- a/rust/helpers/time.c
> +++ b/rust/helpers/time.c
> @@ -33,3 +33,8 @@ s64 rust_helper_ktime_to_ms(const ktime_t kt)
> {
> return ktime_to_ms(kt);
> }
> +
> +void rust_helper_udelay(unsigned long usec)
> +{
> + udelay(usec);
> +}
> diff --git a/rust/kernel/time/delay.rs b/rust/kernel/time/delay.rs
> index eb8838da62bc..baae3238d419 100644
> --- a/rust/kernel/time/delay.rs
> +++ b/rust/kernel/time/delay.rs
> @@ -47,3 +47,37 @@ pub fn fsleep(delta: Delta) {
>         bindings::fsleep(delta.as_micros_ceil() as c_ulong)
>     }
> }
> +
> +/// Inserts a delay based on microseconds with busy waiting.
> +///
> +/// Equivalent to the C side [`udelay()`], which delays in microseconds.
> +///
> +/// `delta` must be within `[0, `MAX_UDELAY_MS`]` in milliseconds;
> +/// otherwise, it is erroneous behavior. That is, it is considered a bug to
> +/// call this function with an out-of-range value, in which case the function
> +/// will insert a delay for at least the maximum value in the range and
> +/// may warn in the future.
> +///
> +/// The behavior above differs from the C side [`udelay()`] for which out-of-range
> +/// values could lead to an overflow and unexpected behavior.
> +///
> +/// [`udelay()`]: https://docs.kernel.org/timers/delay_sleep_functions.html#c.udelay
> +pub fn udelay(delta: Delta) {
> +    const MAX_UDELAY_DELTA: Delta = Delta::from_millis(bindings::MAX_UDELAY_MS as i64);

We should perhaps add a build_assert here to make sure this cast is always valid?

> +
> +    let delta = if (Delta::ZERO..=MAX_UDELAY_DELTA).contains(&delta) {
> +        delta
> +    } else {
> +        // TODO: Add WARN_ONCE() when it's supported.
> +        MAX_UDELAY_DELTA
> +    };
> +
> +    // SAFETY: It is always safe to call `udelay()` with any duration.
> +    unsafe {
> +        // Convert the duration to microseconds and round up to preserve
> +        // the guarantee; `udelay()` inserts a delay for at least
> +        // the provided duration, but that it may delay for longer
> +        // under some circumstances.
> +        bindings::udelay(delta.as_micros_ceil() as c_ulong)
> +    }
> +}
> -- 
> 2.43.0
> 
> 

With the change you suggested for the safety comment in udelay:

Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>

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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-21  3:57 ` [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function FUJITA Tomonori
@ 2025-08-26 14:02   ` Daniel Almeida
  2025-08-27  0:35     ` FUJITA Tomonori
  2025-08-26 14:12   ` Danilo Krummrich
  1 sibling, 1 reply; 22+ messages in thread
From: Daniel Almeida @ 2025-08-26 14:02 UTC (permalink / raw)
  To: FUJITA Tomonori
  Cc: a.hindborg, alex.gaynor, ojeda, aliceryhl, anna-maria, bjorn3_gh,
	boqun.feng, dakr, frederic, gary, jstultz, linux-kernel, lossin,
	lyude, rust-for-linux, sboyd, tglx, tmgross, acourbot

Hi Fujita,

> On 21 Aug 2025, at 00:57, FUJITA Tomonori <fujita.tomonori@gmail.com> wrote:
> 
> Add read_poll_timeout_atomic function which polls periodically until a
> condition is met, an error occurs, or the timeout is reached.
> 
> The C's read_poll_timeout_atomic (include/linux/iopoll.h) is a
> complicated macro and a simple wrapper for Rust doesn't work. So this
> implements the same functionality in Rust.
> 
> The delay_before_read argument isn't supported since there is no user
> for now. It's rarely used in the C version.
> 
> Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
> ---
> rust/kernel/io/poll.rs | 90 +++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 89 insertions(+), 1 deletion(-)
> 
> diff --git a/rust/kernel/io/poll.rs b/rust/kernel/io/poll.rs
> index 7af1934e397a..71c2c0e0d8b4 100644
> --- a/rust/kernel/io/poll.rs
> +++ b/rust/kernel/io/poll.rs
> @@ -8,7 +8,10 @@
>     error::{code::*, Result},
>     processor::cpu_relax,
>     task::might_sleep,
> -    time::{delay::fsleep, Delta, Instant, Monotonic},
> +    time::{
> +        delay::{fsleep, udelay},
> +        Delta, Instant, Monotonic,
> +    },
> };
> 
> /// Polls periodically until a condition is met, an error occurs,
> @@ -102,3 +105,88 @@ pub fn read_poll_timeout<Op, Cond, T>(
>         cpu_relax();
>     }
> }
> +
> +/// Polls periodically until a condition is met, an error occurs,
> +/// or the timeout is reached.
> +///
> +/// The function repeatedly executes the given operation `op` closure and
> +/// checks its result using the condition closure `cond`.
> +///
> +/// If `cond` returns `true`, the function returns successfully with the result of `op`.
> +/// Otherwise, it performs a busy wait for a duration specified by `delay_delta`
> +/// before executing `op` again.
> +///
> +/// This process continues until either `op` returns an error, `cond`
> +/// returns `true`, or the timeout specified by `timeout_delta` is
> +/// reached.
> +///
> +/// # Errors
> +///
> +/// If `op` returns an error, then that error is returned directly.
> +///
> +/// If the timeout specified by `timeout_delta` is reached, then
> +/// `Err(ETIMEDOUT)` is returned.
> +///
> +/// # Examples
> +///
> +/// ```no_run
> +/// use kernel::io::{Io, poll::read_poll_timeout_atomic};
> +/// use kernel::time::Delta;
> +///
> +/// const HW_READY: u16 = 0x01;
> +///
> +/// fn wait_for_hardware<const SIZE: usize>(io: &Io<SIZE>) -> Result<()> {

Just “Result”.

> +///     match read_poll_timeout_atomic(
> +///         // The `op` closure reads the value of a specific status register.
> +///         || io.try_read16(0x1000),
> +///         // The `cond` closure takes a reference to the value returned by `op`
> +///         // and checks whether the hardware is ready.
> +///         |val: &u16| *val == HW_READY,
> +///         Delta::from_micros(50),
> +///         Delta::from_micros(300),
> +///     ) {
> +///         Ok(_) => {
> +///             // The hardware is ready. The returned value of the `op` closure
> +///             // isn't used.
> +///             Ok(())
> +///         }
> +///         Err(e) => Err(e),
> +///     }
> +/// }
> +/// ```
> +pub fn read_poll_timeout_atomic<Op, Cond, T>(
> +    mut op: Op,
> +    mut cond: Cond,
> +    delay_delta: Delta,
> +    timeout_delta: Delta,
> +) -> Result<T>
> +where
> +    Op: FnMut() -> Result<T>,
> +    Cond: FnMut(&T) -> bool,
> +{
> +    let mut left_ns = timeout_delta.as_nanos();
> +    let delay_ns = delay_delta.as_nanos();
> +
> +    loop {
> +        let val = op()?;
> +        if cond(&val) {
> +            // Unlike the C version, we immediately return.
> +            // We know the condition is met so we don't need to check again.
> +            return Ok(val);
> +        }
> +
> +        if left_ns < 0 {
> +            // Unlike the C version, we immediately return.
> +            // We have just called `op()` so we don't need to call it again.
> +            return Err(ETIMEDOUT);
> +        }
> +
> +        if !delay_delta.is_zero() {
> +            udelay(delay_delta);
> +            left_ns -= delay_ns;
> +        }
> +
> +        cpu_relax();
> +        left_ns -= 1;

A comment on the line above would be nice.

Also, is timeout_delta == 0 an intended use-case?

> +    }
> +}
> -- 
> 2.43.0
> 
> 


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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-21  3:57 ` [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function FUJITA Tomonori
  2025-08-26 14:02   ` Daniel Almeida
@ 2025-08-26 14:12   ` Danilo Krummrich
  2025-08-26 16:59     ` Daniel Almeida
  2025-08-27  0:14     ` FUJITA Tomonori
  1 sibling, 2 replies; 22+ messages in thread
From: Danilo Krummrich @ 2025-08-26 14:12 UTC (permalink / raw)
  To: FUJITA Tomonori
  Cc: a.hindborg, alex.gaynor, ojeda, aliceryhl, anna-maria, bjorn3_gh,
	boqun.feng, frederic, gary, jstultz, linux-kernel, lossin, lyude,
	rust-for-linux, sboyd, tglx, tmgross, acourbot, daniel.almeida

On Thu Aug 21, 2025 at 5:57 AM CEST, FUJITA Tomonori wrote:
> +pub fn read_poll_timeout_atomic<Op, Cond, T>(
> +    mut op: Op,
> +    mut cond: Cond,
> +    delay_delta: Delta,
> +    timeout_delta: Delta,
> +) -> Result<T>
> +where
> +    Op: FnMut() -> Result<T>,
> +    Cond: FnMut(&T) -> bool,
> +{
> +    let mut left_ns = timeout_delta.as_nanos();
> +    let delay_ns = delay_delta.as_nanos();
> +
> +    loop {
> +        let val = op()?;
> +        if cond(&val) {
> +            // Unlike the C version, we immediately return.
> +            // We know the condition is met so we don't need to check again.
> +            return Ok(val);
> +        }
> +
> +        if left_ns < 0 {
> +            // Unlike the C version, we immediately return.
> +            // We have just called `op()` so we don't need to call it again.
> +            return Err(ETIMEDOUT);
> +        }
> +
> +        if !delay_delta.is_zero() {
> +            udelay(delay_delta);
> +            left_ns -= delay_ns;
> +        }
> +
> +        cpu_relax();
> +        left_ns -= 1;

How do we know that each iteration costs 1ns? To make it even more obvious, we
don't control the implementation of cond(). Shouldn't we use ktime for this?

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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-26 14:12   ` Danilo Krummrich
@ 2025-08-26 16:59     ` Daniel Almeida
  2025-08-26 17:15       ` Danilo Krummrich
  2025-08-27  0:14     ` FUJITA Tomonori
  1 sibling, 1 reply; 22+ messages in thread
From: Daniel Almeida @ 2025-08-26 16:59 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: FUJITA Tomonori, a.hindborg, alex.gaynor, ojeda, aliceryhl,
	anna-maria, bjorn3_gh, boqun.feng, frederic, gary, jstultz,
	linux-kernel, lossin, lyude, rust-for-linux, sboyd, tglx, tmgross,
	acourbot



> On 26 Aug 2025, at 11:12, Danilo Krummrich <dakr@kernel.org> wrote:
> 
> On Thu Aug 21, 2025 at 5:57 AM CEST, FUJITA Tomonori wrote:
>> +pub fn read_poll_timeout_atomic<Op, Cond, T>(
>> +    mut op: Op,
>> +    mut cond: Cond,
>> +    delay_delta: Delta,
>> +    timeout_delta: Delta,
>> +) -> Result<T>
>> +where
>> +    Op: FnMut() -> Result<T>,
>> +    Cond: FnMut(&T) -> bool,
>> +{
>> +    let mut left_ns = timeout_delta.as_nanos();
>> +    let delay_ns = delay_delta.as_nanos();
>> +
>> +    loop {
>> +        let val = op()?;
>> +        if cond(&val) {
>> +            // Unlike the C version, we immediately return.
>> +            // We know the condition is met so we don't need to check again.
>> +            return Ok(val);
>> +        }
>> +
>> +        if left_ns < 0 {
>> +            // Unlike the C version, we immediately return.
>> +            // We have just called `op()` so we don't need to call it again.
>> +            return Err(ETIMEDOUT);
>> +        }
>> +
>> +        if !delay_delta.is_zero() {
>> +            udelay(delay_delta);
>> +            left_ns -= delay_ns;
>> +        }
>> +
>> +        cpu_relax();
>> +        left_ns -= 1;
> 
> How do we know that each iteration costs 1ns? To make it even more obvious, we
> don't control the implementation of cond(). Shouldn't we use ktime for this?

I don’t think ktime can be used from an atomic context?

— Daniel




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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-26 16:59     ` Daniel Almeida
@ 2025-08-26 17:15       ` Danilo Krummrich
  0 siblings, 0 replies; 22+ messages in thread
From: Danilo Krummrich @ 2025-08-26 17:15 UTC (permalink / raw)
  To: Daniel Almeida
  Cc: FUJITA Tomonori, a.hindborg, alex.gaynor, ojeda, aliceryhl,
	anna-maria, bjorn3_gh, boqun.feng, frederic, gary, jstultz,
	linux-kernel, lossin, lyude, rust-for-linux, sboyd, tglx, tmgross,
	acourbot

On Tue Aug 26, 2025 at 6:59 PM CEST, Daniel Almeida wrote:
>
>
>> On 26 Aug 2025, at 11:12, Danilo Krummrich <dakr@kernel.org> wrote:
>> 
>> On Thu Aug 21, 2025 at 5:57 AM CEST, FUJITA Tomonori wrote:
>>> +pub fn read_poll_timeout_atomic<Op, Cond, T>(
>>> +    mut op: Op,
>>> +    mut cond: Cond,
>>> +    delay_delta: Delta,
>>> +    timeout_delta: Delta,
>>> +) -> Result<T>
>>> +where
>>> +    Op: FnMut() -> Result<T>,
>>> +    Cond: FnMut(&T) -> bool,
>>> +{
>>> +    let mut left_ns = timeout_delta.as_nanos();
>>> +    let delay_ns = delay_delta.as_nanos();
>>> +
>>> +    loop {
>>> +        let val = op()?;
>>> +        if cond(&val) {
>>> +            // Unlike the C version, we immediately return.
>>> +            // We know the condition is met so we don't need to check again.
>>> +            return Ok(val);
>>> +        }
>>> +
>>> +        if left_ns < 0 {
>>> +            // Unlike the C version, we immediately return.
>>> +            // We have just called `op()` so we don't need to call it again.
>>> +            return Err(ETIMEDOUT);
>>> +        }
>>> +
>>> +        if !delay_delta.is_zero() {
>>> +            udelay(delay_delta);
>>> +            left_ns -= delay_ns;
>>> +        }
>>> +
>>> +        cpu_relax();
>>> +        left_ns -= 1;
>> 
>> How do we know that each iteration costs 1ns? To make it even more obvious, we
>> don't control the implementation of cond(). Shouldn't we use ktime for this?
>
> I don’t think ktime can be used from an atomic context?

There's no problem calling things like ktime_get() from atomic context.

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

* Re: [PATCH v1 1/2] rust: add udelay() function
  2025-08-26 11:59     ` FUJITA Tomonori
@ 2025-08-26 18:03       ` Miguel Ojeda
  2025-08-27  7:12         ` Andreas Hindborg
  0 siblings, 1 reply; 22+ messages in thread
From: Miguel Ojeda @ 2025-08-26 18:03 UTC (permalink / raw)
  To: FUJITA Tomonori
  Cc: a.hindborg, alex.gaynor, ojeda, aliceryhl, anna-maria, bjorn3_gh,
	boqun.feng, dakr, frederic, gary, jstultz, linux-kernel, lossin,
	lyude, rust-for-linux, sboyd, tglx, tmgross, acourbot,
	daniel.almeida

On Tue, Aug 26, 2025 at 1:59 PM FUJITA Tomonori
<fujita.tomonori@gmail.com> wrote:
>
> This can lead to an unexpected delay duration, but it's safe in Rust’s
> sense of safety?

If it is just unexpected behavior, then yeah.

Perhaps Andreas is referring to C overflow UB? If that is the case,
then in the kernel it is actually defined due to
`-fno-strict-overflow`.

Cheers,
Miguel

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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-26 14:12   ` Danilo Krummrich
  2025-08-26 16:59     ` Daniel Almeida
@ 2025-08-27  0:14     ` FUJITA Tomonori
  2025-08-27  9:00       ` Danilo Krummrich
  1 sibling, 1 reply; 22+ messages in thread
From: FUJITA Tomonori @ 2025-08-27  0:14 UTC (permalink / raw)
  To: dakr
  Cc: fujita.tomonori, a.hindborg, alex.gaynor, ojeda, aliceryhl,
	anna-maria, bjorn3_gh, boqun.feng, frederic, gary, jstultz,
	linux-kernel, lossin, lyude, rust-for-linux, sboyd, tglx, tmgross,
	acourbot, daniel.almeida

On Tue, 26 Aug 2025 16:12:44 +0200
"Danilo Krummrich" <dakr@kernel.org> wrote:

> On Thu Aug 21, 2025 at 5:57 AM CEST, FUJITA Tomonori wrote:
>> +pub fn read_poll_timeout_atomic<Op, Cond, T>(
>> +    mut op: Op,
>> +    mut cond: Cond,
>> +    delay_delta: Delta,
>> +    timeout_delta: Delta,
>> +) -> Result<T>
>> +where
>> +    Op: FnMut() -> Result<T>,
>> +    Cond: FnMut(&T) -> bool,
>> +{
>> +    let mut left_ns = timeout_delta.as_nanos();
>> +    let delay_ns = delay_delta.as_nanos();
>> +
>> +    loop {
>> +        let val = op()?;
>> +        if cond(&val) {
>> +            // Unlike the C version, we immediately return.
>> +            // We know the condition is met so we don't need to check again.
>> +            return Ok(val);
>> +        }
>> +
>> +        if left_ns < 0 {
>> +            // Unlike the C version, we immediately return.
>> +            // We have just called `op()` so we don't need to call it again.
>> +            return Err(ETIMEDOUT);
>> +        }
>> +
>> +        if !delay_delta.is_zero() {
>> +            udelay(delay_delta);
>> +            left_ns -= delay_ns;
>> +        }
>> +
>> +        cpu_relax();
>> +        left_ns -= 1;
> 
> How do we know that each iteration costs 1ns? To make it even more obvious, we
> don't control the implementation of cond(). Shouldn't we use ktime for this?

The C version used to use ktime but it has been changed not to:

7349a69cf312 ("iopoll: Do not use timekeeping in read_poll_timeout_atomic()")

https://lore.kernel.org/all/3d2a2f4e553489392d871108797c3be08f88300b.1685692810.git.geert+renesas@glider.be/

I don’t know if the same problem still exists, but I think we should
follow the C implementation. Usually there’s a good reason behind it,
and it has been working so far.

If we want to do it differently in Rust, maybe we should first discuss
the C implementation.


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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-26 14:02   ` Daniel Almeida
@ 2025-08-27  0:35     ` FUJITA Tomonori
  2025-08-27  4:32       ` FUJITA Tomonori
  0 siblings, 1 reply; 22+ messages in thread
From: FUJITA Tomonori @ 2025-08-27  0:35 UTC (permalink / raw)
  To: daniel.almeida
  Cc: fujita.tomonori, a.hindborg, alex.gaynor, ojeda, aliceryhl,
	anna-maria, bjorn3_gh, boqun.feng, dakr, frederic, gary, jstultz,
	linux-kernel, lossin, lyude, rust-for-linux, sboyd, tglx, tmgross,
	acourbot

On Tue, 26 Aug 2025 11:02:18 -0300
Daniel Almeida <daniel.almeida@collabora.com> wrote:

>> +/// Polls periodically until a condition is met, an error occurs,
>> +/// or the timeout is reached.
>> +///
>> +/// The function repeatedly executes the given operation `op` closure and
>> +/// checks its result using the condition closure `cond`.
>> +///
>> +/// If `cond` returns `true`, the function returns successfully with the result of `op`.
>> +/// Otherwise, it performs a busy wait for a duration specified by `delay_delta`
>> +/// before executing `op` again.
>> +///
>> +/// This process continues until either `op` returns an error, `cond`
>> +/// returns `true`, or the timeout specified by `timeout_delta` is
>> +/// reached.
>> +///
>> +/// # Errors
>> +///
>> +/// If `op` returns an error, then that error is returned directly.
>> +///
>> +/// If the timeout specified by `timeout_delta` is reached, then
>> +/// `Err(ETIMEDOUT)` is returned.
>> +///
>> +/// # Examples
>> +///
>> +/// ```no_run
>> +/// use kernel::io::{Io, poll::read_poll_timeout_atomic};
>> +/// use kernel::time::Delta;
>> +///
>> +/// const HW_READY: u16 = 0x01;
>> +///
>> +/// fn wait_for_hardware<const SIZE: usize>(io: &Io<SIZE>) -> Result<()> {
> 
> Just “Result”.

Oops, thanks.

I'll update the example for read_poll_timeout() too.


>> +///     match read_poll_timeout_atomic(
>> +///         // The `op` closure reads the value of a specific status register.
>> +///         || io.try_read16(0x1000),
>> +///         // The `cond` closure takes a reference to the value returned by `op`
>> +///         // and checks whether the hardware is ready.
>> +///         |val: &u16| *val == HW_READY,
>> +///         Delta::from_micros(50),
>> +///         Delta::from_micros(300),
>> +///     ) {
>> +///         Ok(_) => {
>> +///             // The hardware is ready. The returned value of the `op` closure
>> +///             // isn't used.
>> +///             Ok(())
>> +///         }
>> +///         Err(e) => Err(e),
>> +///     }
>> +/// }
>> +/// ```
>> +pub fn read_poll_timeout_atomic<Op, Cond, T>(
>> +    mut op: Op,
>> +    mut cond: Cond,
>> +    delay_delta: Delta,
>> +    timeout_delta: Delta,
>> +) -> Result<T>
>> +where
>> +    Op: FnMut() -> Result<T>,
>> +    Cond: FnMut(&T) -> bool,
>> +{
>> +    let mut left_ns = timeout_delta.as_nanos();
>> +    let delay_ns = delay_delta.as_nanos();
>> +
>> +    loop {
>> +        let val = op()?;
>> +        if cond(&val) {
>> +            // Unlike the C version, we immediately return.
>> +            // We know the condition is met so we don't need to check again.
>> +            return Ok(val);
>> +        }
>> +
>> +        if left_ns < 0 {
>> +            // Unlike the C version, we immediately return.
>> +            // We have just called `op()` so we don't need to call it again.
>> +            return Err(ETIMEDOUT);
>> +        }
>> +
>> +        if !delay_delta.is_zero() {
>> +            udelay(delay_delta);
>> +            left_ns -= delay_ns;
>> +        }
>> +
>> +        cpu_relax();
>> +        left_ns -= 1;
> 
> A comment on the line above would be nice.

As I wrote in another email, the C version was changed to avoid using
ktime, and that’s when the code above was added. I assume the delay is
considered as 1ns as a compromise because ktime can’t be used.

Maybe this comment should be added to the C version instead?


> Also, is timeout_delta == 0 an intended use-case?

I’m not sure if it’s actually used, but I don’t see any reason to
forbid it.


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

* Re: [PATCH v1 1/2] rust: add udelay() function
  2025-08-26 12:44   ` Daniel Almeida
@ 2025-08-27  2:43     ` FUJITA Tomonori
  0 siblings, 0 replies; 22+ messages in thread
From: FUJITA Tomonori @ 2025-08-27  2:43 UTC (permalink / raw)
  To: daniel.almeida
  Cc: fujita.tomonori, a.hindborg, alex.gaynor, ojeda, aliceryhl,
	anna-maria, bjorn3_gh, boqun.feng, dakr, frederic, gary, jstultz,
	linux-kernel, lossin, lyude, rust-for-linux, sboyd, tglx, tmgross,
	acourbot

On Tue, 26 Aug 2025 09:44:27 -0300
Daniel Almeida <daniel.almeida@collabora.com> wrote:

>> +/// Inserts a delay based on microseconds with busy waiting.
>> +///
>> +/// Equivalent to the C side [`udelay()`], which delays in microseconds.
>> +///
>> +/// `delta` must be within `[0, `MAX_UDELAY_MS`]` in milliseconds;
>> +/// otherwise, it is erroneous behavior. That is, it is considered a bug to
>> +/// call this function with an out-of-range value, in which case the function
>> +/// will insert a delay for at least the maximum value in the range and
>> +/// may warn in the future.
>> +///
>> +/// The behavior above differs from the C side [`udelay()`] for which out-of-range
>> +/// values could lead to an overflow and unexpected behavior.
>> +///
>> +/// [`udelay()`]: https://docs.kernel.org/timers/delay_sleep_functions.html#c.udelay
>> +pub fn udelay(delta: Delta) {
>> +    const MAX_UDELAY_DELTA: Delta = Delta::from_millis(bindings::MAX_UDELAY_MS as i64);
> 
> We should perhaps add a build_assert here to make sure this cast is always valid?

Are you concerned that bindings::MAX_UDELAY_MS might exceed i64::MAX?

If so, such a value seems unrealistic as a delay. While
bindings::MAX_UDELAY_MS is architecture-dependent, its maximum is
currently 10, so I don’t think this will ever become an issue in the
future.

If really necessary, we could add something like the followings?

build_assert!(i128::from(bindings::MAX_UDELAY_MS) < i64::MAX.into());

>> +
>> +    let delta = if (Delta::ZERO..=MAX_UDELAY_DELTA).contains(&delta) {
>> +        delta
>> +    } else {
>> +        // TODO: Add WARN_ONCE() when it's supported.
>> +        MAX_UDELAY_DELTA
>> +    };
>> +
>> +    // SAFETY: It is always safe to call `udelay()` with any duration.
>> +    unsafe {
>> +        // Convert the duration to microseconds and round up to preserve
>> +        // the guarantee; `udelay()` inserts a delay for at least
>> +        // the provided duration, but that it may delay for longer
>> +        // under some circumstances.
>> +        bindings::udelay(delta.as_micros_ceil() as c_ulong)
>> +    }
>> +}
>> -- 
>> 2.43.0
>> 
>> 
> 
> With the change you suggested for the safety comment in udelay:
> 
> Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>

As Miguel wrote, any duration is safe from Rust’s perspective, and it
won’t invoke UB on the C side either.

So I'm not sure the above safety comment needs to be changed. Let’s
ask for Andreas’s opinion.


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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-27  0:35     ` FUJITA Tomonori
@ 2025-08-27  4:32       ` FUJITA Tomonori
  0 siblings, 0 replies; 22+ messages in thread
From: FUJITA Tomonori @ 2025-08-27  4:32 UTC (permalink / raw)
  To: daniel.almeida
  Cc: a.hindborg, alex.gaynor, ojeda, aliceryhl, anna-maria, bjorn3_gh,
	boqun.feng, dakr, frederic, gary, jstultz, linux-kernel, lossin,
	lyude, rust-for-linux, sboyd, tglx, tmgross, acourbot

On Wed, 27 Aug 2025 09:35:59 +0900 (JST)
FUJITA Tomonori <fujita.tomonori@gmail.com> wrote:

>>> +pub fn read_poll_timeout_atomic<Op, Cond, T>(
>>> +    mut op: Op,
>>> +    mut cond: Cond,
>>> +    delay_delta: Delta,
>>> +    timeout_delta: Delta,
>>> +) -> Result<T>
>>> +where
>>> +    Op: FnMut() -> Result<T>,
>>> +    Cond: FnMut(&T) -> bool,
>>> +{
>>> +    let mut left_ns = timeout_delta.as_nanos();
>>> +    let delay_ns = delay_delta.as_nanos();
>>> +
>>> +    loop {
>>> +        let val = op()?;
>>> +        if cond(&val) {
>>> +            // Unlike the C version, we immediately return.
>>> +            // We know the condition is met so we don't need to check again.
>>> +            return Ok(val);
>>> +        }
>>> +
>>> +        if left_ns < 0 {
>>> +            // Unlike the C version, we immediately return.
>>> +            // We have just called `op()` so we don't need to call it again.
>>> +            return Err(ETIMEDOUT);
>>> +        }
>>> +
>>> +        if !delay_delta.is_zero() {
>>> +            udelay(delay_delta);
>>> +            left_ns -= delay_ns;
>>> +        }
>>> +
>>> +        cpu_relax();
>>> +        left_ns -= 1;
>> 
>> A comment on the line above would be nice.
> 
> As I wrote in another email, the C version was changed to avoid using
> ktime, and that’s when the code above was added. I assume the delay is
> considered as 1ns as a compromise because ktime can’t be used.
> 
> Maybe this comment should be added to the C version instead?

I meant that if we add a comment here, maybe it should be added to the
C version.

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

* Re: [PATCH v1 1/2] rust: add udelay() function
  2025-08-26 18:03       ` Miguel Ojeda
@ 2025-08-27  7:12         ` Andreas Hindborg
  0 siblings, 0 replies; 22+ messages in thread
From: Andreas Hindborg @ 2025-08-27  7:12 UTC (permalink / raw)
  To: Miguel Ojeda, FUJITA Tomonori
  Cc: alex.gaynor, ojeda, aliceryhl, anna-maria, bjorn3_gh, boqun.feng,
	dakr, frederic, gary, jstultz, linux-kernel, lossin, lyude,
	rust-for-linux, sboyd, tglx, tmgross, acourbot, daniel.almeida

"Miguel Ojeda" <miguel.ojeda.sandonis@gmail.com> writes:

> On Tue, Aug 26, 2025 at 1:59 PM FUJITA Tomonori
> <fujita.tomonori@gmail.com> wrote:
>>
>> This can lead to an unexpected delay duration, but it's safe in Rust’s
>> sense of safety?
>
> If it is just unexpected behavior, then yeah.
>
> Perhaps Andreas is referring to C overflow UB? If that is the case,
> then in the kernel it is actually defined due to
> `-fno-strict-overflow`.

OK, cool Then I would suggest that we just add a small note in the docs
about the C behavior that even though passing an invalid value is
considered a bug, it will not cause UB or memory unsafety.


Best regards,
Andreas Hindborg




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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-27  0:14     ` FUJITA Tomonori
@ 2025-08-27  9:00       ` Danilo Krummrich
  2025-08-27 10:29         ` Danilo Krummrich
  0 siblings, 1 reply; 22+ messages in thread
From: Danilo Krummrich @ 2025-08-27  9:00 UTC (permalink / raw)
  To: FUJITA Tomonori
  Cc: a.hindborg, alex.gaynor, ojeda, aliceryhl, anna-maria, bjorn3_gh,
	boqun.feng, frederic, gary, jstultz, linux-kernel, lossin, lyude,
	rust-for-linux, sboyd, tglx, tmgross, acourbot, daniel.almeida

On Wed Aug 27, 2025 at 2:14 AM CEST, FUJITA Tomonori wrote:
> On Tue, 26 Aug 2025 16:12:44 +0200
> "Danilo Krummrich" <dakr@kernel.org> wrote:
>
>> On Thu Aug 21, 2025 at 5:57 AM CEST, FUJITA Tomonori wrote:
>>> +pub fn read_poll_timeout_atomic<Op, Cond, T>(
>>> +    mut op: Op,
>>> +    mut cond: Cond,
>>> +    delay_delta: Delta,
>>> +    timeout_delta: Delta,
>>> +) -> Result<T>
>>> +where
>>> +    Op: FnMut() -> Result<T>,
>>> +    Cond: FnMut(&T) -> bool,
>>> +{
>>> +    let mut left_ns = timeout_delta.as_nanos();
>>> +    let delay_ns = delay_delta.as_nanos();
>>> +
>>> +    loop {
>>> +        let val = op()?;
>>> +        if cond(&val) {
>>> +            // Unlike the C version, we immediately return.
>>> +            // We know the condition is met so we don't need to check again.
>>> +            return Ok(val);
>>> +        }
>>> +
>>> +        if left_ns < 0 {
>>> +            // Unlike the C version, we immediately return.
>>> +            // We have just called `op()` so we don't need to call it again.
>>> +            return Err(ETIMEDOUT);
>>> +        }
>>> +
>>> +        if !delay_delta.is_zero() {
>>> +            udelay(delay_delta);
>>> +            left_ns -= delay_ns;
>>> +        }
>>> +
>>> +        cpu_relax();
>>> +        left_ns -= 1;
>> 
>> How do we know that each iteration costs 1ns? To make it even more obvious, we
>> don't control the implementation of cond(). Shouldn't we use ktime for this?
>
> The C version used to use ktime but it has been changed not to:
>
> 7349a69cf312 ("iopoll: Do not use timekeeping in read_poll_timeout_atomic()")

Ick! That's pretty unfortunate -- no ktime then.

But regardless of that, the current implementation (this and the C one) lack
clarity.

The nanosecond decrement is rather negligible, the real timeout reduction comes
from the delay_delta. Given that, and the fact that we can't use ktime, this
function shouldn't take a raw timeout value, since we can't guarantee the
timeout anyways.

Instead, I think it makes much more sense to provide a retry count as function
argument, such that the user can specify "I want a dealy of 100us, try it 100
times".

This way it is transparent to the caller that the timeout may be significantly
more than 10ms depending on the user's implementation.

As for doing this in C vs Rust: I don't think things have to align in every
implementation detail. If we can improve things on the Rust side from the
get-go, we should not stop ourselves from doing so, just because a similar C
implementation is hard to refactor, due to having a lot of users already.

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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-27  9:00       ` Danilo Krummrich
@ 2025-08-27 10:29         ` Danilo Krummrich
  2025-08-27 12:14           ` Daniel Almeida
  0 siblings, 1 reply; 22+ messages in thread
From: Danilo Krummrich @ 2025-08-27 10:29 UTC (permalink / raw)
  To: FUJITA Tomonori
  Cc: a.hindborg, alex.gaynor, ojeda, aliceryhl, anna-maria, bjorn3_gh,
	boqun.feng, frederic, gary, jstultz, linux-kernel, lossin, lyude,
	rust-for-linux, sboyd, tglx, tmgross, acourbot, daniel.almeida

On Wed Aug 27, 2025 at 11:00 AM CEST, Danilo Krummrich wrote:
> On Wed Aug 27, 2025 at 2:14 AM CEST, FUJITA Tomonori wrote:
>> On Tue, 26 Aug 2025 16:12:44 +0200
>> "Danilo Krummrich" <dakr@kernel.org> wrote:
>>
>>> On Thu Aug 21, 2025 at 5:57 AM CEST, FUJITA Tomonori wrote:
>>>> +pub fn read_poll_timeout_atomic<Op, Cond, T>(
>>>> +    mut op: Op,
>>>> +    mut cond: Cond,
>>>> +    delay_delta: Delta,
>>>> +    timeout_delta: Delta,
>>>> +) -> Result<T>
>>>> +where
>>>> +    Op: FnMut() -> Result<T>,
>>>> +    Cond: FnMut(&T) -> bool,
>>>> +{
>>>> +    let mut left_ns = timeout_delta.as_nanos();
>>>> +    let delay_ns = delay_delta.as_nanos();
>>>> +
>>>> +    loop {
>>>> +        let val = op()?;
>>>> +        if cond(&val) {
>>>> +            // Unlike the C version, we immediately return.
>>>> +            // We know the condition is met so we don't need to check again.
>>>> +            return Ok(val);
>>>> +        }
>>>> +
>>>> +        if left_ns < 0 {
>>>> +            // Unlike the C version, we immediately return.
>>>> +            // We have just called `op()` so we don't need to call it again.
>>>> +            return Err(ETIMEDOUT);
>>>> +        }
>>>> +
>>>> +        if !delay_delta.is_zero() {
>>>> +            udelay(delay_delta);
>>>> +            left_ns -= delay_ns;
>>>> +        }
>>>> +
>>>> +        cpu_relax();
>>>> +        left_ns -= 1;
>>> 
>>> How do we know that each iteration costs 1ns? To make it even more obvious, we
>>> don't control the implementation of cond(). Shouldn't we use ktime for this?
>>
>> The C version used to use ktime but it has been changed not to:
>>
>> 7349a69cf312 ("iopoll: Do not use timekeeping in read_poll_timeout_atomic()")
>
> Ick! That's pretty unfortunate -- no ktime then.
>
> But regardless of that, the current implementation (this and the C one) lack
> clarity.
>
> The nanosecond decrement is rather negligible, the real timeout reduction comes
> from the delay_delta. Given that, and the fact that we can't use ktime, this
> function shouldn't take a raw timeout value, since we can't guarantee the
> timeout anyways.

Actually, let me put it in other words:

	let val = read_poll_timeout_atomic(
	    || {
	        // Fetch the offset to read from from the HW.
	        let offset = io.read32(0x1000);
	
	        // HW needs a break for some odd reason.
	        udelay(100);
	
	        // Read the actual value.
	        io.try_read32(offset)
	    },
	    |val: &u32| *val == HW_READY,
	    Delta::from_micros(0),      // No delay, keep spinning.
	    Delta::from_millis(10),     // Timeout after 10ms.
	)?;

Seems like a fairly reasonable usage without knowing the implementation details
of read_poll_timeout_atomic(), right?

Except that if the hardware does not become ready, this will spin for 16.67
*minutes* -- in atomic context. Instead of the 10ms the user would expect.

This would be way less error prone if we do not provide a timeout value, but a
retry count.

> Instead, I think it makes much more sense to provide a retry count as function
> argument, such that the user can specify "I want a dealy of 100us, try it 100
> times".
>
> This way it is transparent to the caller that the timeout may be significantly
> more than 10ms depending on the user's implementation.
>
> As for doing this in C vs Rust: I don't think things have to align in every
> implementation detail. If we can improve things on the Rust side from the
> get-go, we should not stop ourselves from doing so, just because a similar C
> implementation is hard to refactor, due to having a lot of users already.

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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-27 10:29         ` Danilo Krummrich
@ 2025-08-27 12:14           ` Daniel Almeida
  2025-08-27 12:19             ` Danilo Krummrich
  0 siblings, 1 reply; 22+ messages in thread
From: Daniel Almeida @ 2025-08-27 12:14 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: FUJITA Tomonori, a.hindborg, alex.gaynor, ojeda, aliceryhl,
	anna-maria, bjorn3_gh, boqun.feng, frederic, gary, jstultz,
	linux-kernel, lossin, lyude, rust-for-linux, sboyd, tglx, tmgross,
	acourbot

Hi Danilo,

[…}

> 
> Actually, let me put it in other words:
> 
> let val = read_poll_timeout_atomic(
>     || {
>         // Fetch the offset to read from from the HW.
>         let offset = io.read32(0x1000);
> 
>         // HW needs a break for some odd reason.
>         udelay(100);
> 
>         // Read the actual value.
>         io.try_read32(offset)
>     },
>     |val: &u32| *val == HW_READY,
>     Delta::from_micros(0),      // No delay, keep spinning.
>     Delta::from_millis(10),     // Timeout after 10ms.
> )?;
> 
> Seems like a fairly reasonable usage without knowing the implementation details
> of read_poll_timeout_atomic(), right?
> 
> Except that if the hardware does not become ready, this will spin for 16.67
> *minutes* -- in atomic context. Instead of the 10ms the user would expect.
> 
> This would be way less error prone if we do not provide a timeout value, but a
> retry count.
> 
>> Instead, I think it makes much more sense to provide a retry count as function
>> argument, such that the user can specify "I want a dealy of 100us, try it 100
>> times".
>> 
>> This way it is transparent to the caller that the timeout may be significantly
>> more than 10ms depending on the user's implementation.
>> 
>> As for doing this in C vs Rust: I don't think things have to align in every
>> implementation detail. If we can improve things on the Rust side from the
>> get-go, we should not stop ourselves from doing so, just because a similar C
>> implementation is hard to refactor, due to having a lot of users already.

I must say I do not follow. Can you expand yet some more on this?

— Daniel



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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-27 12:14           ` Daniel Almeida
@ 2025-08-27 12:19             ` Danilo Krummrich
  2025-08-27 12:22               ` Daniel Almeida
  0 siblings, 1 reply; 22+ messages in thread
From: Danilo Krummrich @ 2025-08-27 12:19 UTC (permalink / raw)
  To: Daniel Almeida
  Cc: FUJITA Tomonori, a.hindborg, alex.gaynor, ojeda, aliceryhl,
	anna-maria, bjorn3_gh, boqun.feng, frederic, gary, jstultz,
	linux-kernel, lossin, lyude, rust-for-linux, sboyd, tglx, tmgross,
	acourbot

On Wed Aug 27, 2025 at 2:14 PM CEST, Daniel Almeida wrote:
> Hi Danilo,
>
> […}
>
>> 
>> Actually, let me put it in other words:
>> 
>> let val = read_poll_timeout_atomic(
>>     || {
>>         // Fetch the offset to read from from the HW.
>>         let offset = io.read32(0x1000);
>> 
>>         // HW needs a break for some odd reason.
>>         udelay(100);
>> 
>>         // Read the actual value.
>>         io.try_read32(offset)
>>     },
>>     |val: &u32| *val == HW_READY,
>>     Delta::from_micros(0),      // No delay, keep spinning.
>>     Delta::from_millis(10),     // Timeout after 10ms.
>> )?;
>> 
>> Seems like a fairly reasonable usage without knowing the implementation details
>> of read_poll_timeout_atomic(), right?
>> 
>> Except that if the hardware does not become ready, this will spin for 16.67
>> *minutes* -- in atomic context. Instead of the 10ms the user would expect.
>> 
>> This would be way less error prone if we do not provide a timeout value, but a
>> retry count.
>> 
>>> Instead, I think it makes much more sense to provide a retry count as function
>>> argument, such that the user can specify "I want a dealy of 100us, try it 100
>>> times".
>>> 
>>> This way it is transparent to the caller that the timeout may be significantly
>>> more than 10ms depending on the user's implementation.
>>> 
>>> As for doing this in C vs Rust: I don't think things have to align in every
>>> implementation detail. If we can improve things on the Rust side from the
>>> get-go, we should not stop ourselves from doing so, just because a similar C
>>> implementation is hard to refactor, due to having a lot of users already.
>
> I must say I do not follow. Can you expand yet some more on this?

Sure, but it would help if you could clarify which aspect you want me to expand
on. :)

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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-27 12:19             ` Danilo Krummrich
@ 2025-08-27 12:22               ` Daniel Almeida
  2025-08-27 12:36                 ` Danilo Krummrich
  0 siblings, 1 reply; 22+ messages in thread
From: Daniel Almeida @ 2025-08-27 12:22 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: FUJITA Tomonori, a.hindborg, alex.gaynor, ojeda, aliceryhl,
	anna-maria, bjorn3_gh, boqun.feng, frederic, gary, jstultz,
	linux-kernel, lossin, lyude, rust-for-linux, sboyd, tglx, tmgross,
	acourbot



> On 27 Aug 2025, at 09:19, Danilo Krummrich <dakr@kernel.org> wrote:
> 
> On Wed Aug 27, 2025 at 2:14 PM CEST, Daniel Almeida wrote:
>> Hi Danilo,
>> 
>> […}
>> 
>>> 
>>> Actually, let me put it in other words:
>>> 
>>> let val = read_poll_timeout_atomic(
>>>    || {
>>>        // Fetch the offset to read from from the HW.
>>>        let offset = io.read32(0x1000);
>>> 
>>>        // HW needs a break for some odd reason.
>>>        udelay(100);

Why would we have a delay here? Can’t this be broken into two calls to
read_poll_timeout_atomic()? That would be equivalent to what you wrote
IIUC.

>>> 
>>>        // Read the actual value.
>>>        io.try_read32(offset)
>>>    },
>>>    |val: &u32| *val == HW_READY,
>>>    Delta::from_micros(0),      // No delay, keep spinning.
>>>    Delta::from_millis(10),     // Timeout after 10ms.
>>> )?;
>>> 
>>> Seems like a fairly reasonable usage without knowing the implementation details
>>> of read_poll_timeout_atomic(), right?
>>> 
>>> Except that if the hardware does not become ready, this will spin for 16.67
>>> *minutes* -- in atomic context. Instead of the 10ms the user would expect.

This is where you lost me. Where does the 16.67 come from?

>>> 
>>> This would be way less error prone if we do not provide a timeout value, but a
>>> retry count.
>>> 
>>>> Instead, I think it makes much more sense to provide a retry count as function
>>>> argument, such that the user can specify "I want a dealy of 100us, try it 100
>>>> times".
>>>> 
>>>> This way it is transparent to the caller that the timeout may be significantly
>>>> more than 10ms depending on the user's implementation.
>>>> 
>>>> As for doing this in C vs Rust: I don't think things have to align in every
>>>> implementation detail. If we can improve things on the Rust side from the
>>>> get-go, we should not stop ourselves from doing so, just because a similar C
>>>> implementation is hard to refactor, due to having a lot of users already.
>> 
>> I must say I do not follow. Can you expand yet some more on this?
> 
> Sure, but it would help if you could clarify which aspect you want me to expand
> on. :)



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

* Re: [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function
  2025-08-27 12:22               ` Daniel Almeida
@ 2025-08-27 12:36                 ` Danilo Krummrich
  0 siblings, 0 replies; 22+ messages in thread
From: Danilo Krummrich @ 2025-08-27 12:36 UTC (permalink / raw)
  To: Daniel Almeida
  Cc: FUJITA Tomonori, a.hindborg, alex.gaynor, ojeda, aliceryhl,
	anna-maria, bjorn3_gh, boqun.feng, frederic, gary, jstultz,
	linux-kernel, lossin, lyude, rust-for-linux, sboyd, tglx, tmgross,
	acourbot

On Wed Aug 27, 2025 at 2:22 PM CEST, Daniel Almeida wrote:
>
>
>> On 27 Aug 2025, at 09:19, Danilo Krummrich <dakr@kernel.org> wrote:
>> 
>> On Wed Aug 27, 2025 at 2:14 PM CEST, Daniel Almeida wrote:
>>> Hi Danilo,
>>> 
>>> […}
>>> 
>>>> 
>>>> Actually, let me put it in other words:
>>>> 
>>>> let val = read_poll_timeout_atomic(
>>>>    || {
>>>>        // Fetch the offset to read from from the HW.
>>>>        let offset = io.read32(0x1000);
>>>> 
>>>>        // HW needs a break for some odd reason.
>>>>        udelay(100);
>
> Why would we have a delay here? Can’t this be broken into two calls to
> read_poll_timeout_atomic()? That would be equivalent to what you wrote
> IIUC.

I'm sure this can somehow be written otherwise as well. But that's not the
point, the point is that this looks like perfectly valid code from a users
perspective.

>>>> 
>>>>        // Read the actual value.
>>>>        io.try_read32(offset)
>>>>    },
>>>>    |val: &u32| *val == HW_READY,
>>>>    Delta::from_micros(0),      // No delay, keep spinning.
>>>>    Delta::from_millis(10),     // Timeout after 10ms.
>>>> )?;
>>>> 
>>>> Seems like a fairly reasonable usage without knowing the implementation details
>>>> of read_poll_timeout_atomic(), right?
>>>> 
>>>> Except that if the hardware does not become ready, this will spin for 16.67
>>>> *minutes* -- in atomic context. Instead of the 10ms the user would expect.
>
> This is where you lost me. Where does the 16.67 come from?

Ah, I see -- let me explain:

Internally read_poll_timeout_atomic() would convert the timeout (10ms) into ns
(let's call it nanos). Then, it would decrement nanos in every iteration of the
internal loop, based on the (wrong) assumption that every loop takes exactly
1ns.

However, since the user executes udelay(100), which is perfectly valid from the
users perspective, in the Op closure, every loop iteration takes at least 100us
instead.

So, the actual timeout calculates as follows.

	Timeout: 10ms = 10.000us = 10.000.000ns

In every iteration this number is decremented by one, hence 10.000.000
iterations.

	100us * 10.000.000 iterations = 16.67 minutes

So, the issue really is that we're not measuring time, but the number of
iterations if delay_delta == 0.

As delay_delta grows the relative eror becomes smaller, yet this is far from
sane behavior.

>>>> 
>>>> This would be way less error prone if we do not provide a timeout value, but a
>>>> retry count.
>>>> 
>>>>> Instead, I think it makes much more sense to provide a retry count as function
>>>>> argument, such that the user can specify "I want a dealy of 100us, try it 100
>>>>> times".
>>>>> 
>>>>> This way it is transparent to the caller that the timeout may be significantly
>>>>> more than 10ms depending on the user's implementation.
>>>>> 
>>>>> As for doing this in C vs Rust: I don't think things have to align in every
>>>>> implementation detail. If we can improve things on the Rust side from the
>>>>> get-go, we should not stop ourselves from doing so, just because a similar C
>>>>> implementation is hard to refactor, due to having a lot of users already.
>>> 
>>> I must say I do not follow. Can you expand yet some more on this?
>> 
>> Sure, but it would help if you could clarify which aspect you want me to expand
>> on. :)


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

end of thread, other threads:[~2025-08-27 12:36 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-21  3:57 [PATCH v1 0/2] Add read_poll_timeout_atomic support FUJITA Tomonori
2025-08-21  3:57 ` [PATCH v1 1/2] rust: add udelay() function FUJITA Tomonori
2025-08-26  9:09   ` Andreas Hindborg
2025-08-26 11:59     ` FUJITA Tomonori
2025-08-26 18:03       ` Miguel Ojeda
2025-08-27  7:12         ` Andreas Hindborg
2025-08-26 12:44   ` Daniel Almeida
2025-08-27  2:43     ` FUJITA Tomonori
2025-08-21  3:57 ` [PATCH v1 2/2] rust: Add read_poll_timeout_atomic function FUJITA Tomonori
2025-08-26 14:02   ` Daniel Almeida
2025-08-27  0:35     ` FUJITA Tomonori
2025-08-27  4:32       ` FUJITA Tomonori
2025-08-26 14:12   ` Danilo Krummrich
2025-08-26 16:59     ` Daniel Almeida
2025-08-26 17:15       ` Danilo Krummrich
2025-08-27  0:14     ` FUJITA Tomonori
2025-08-27  9:00       ` Danilo Krummrich
2025-08-27 10:29         ` Danilo Krummrich
2025-08-27 12:14           ` Daniel Almeida
2025-08-27 12:19             ` Danilo Krummrich
2025-08-27 12:22               ` Daniel Almeida
2025-08-27 12:36                 ` Danilo Krummrich

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).