[PATCH v2 2/2] rust: Add read_poll_count_atomic function

FUJITA Tomonori posted 2 patches 3 months, 3 weeks ago
[PATCH v2 2/2] rust: Add read_poll_count_atomic function
Posted by FUJITA Tomonori 3 months, 3 weeks ago
Add read_poll_count_atomic function which polls periodically until a
condition is met, an error occurs, or the attempt limit is reached.

The C's read_poll_timeout_atomic() is used for the similar purpose.
In atomic context the timekeeping infrastructure is unavailable, so
reliable time-based timeouts cannot be implemented. So instead, the
helper accepts a maximum number of attempts and busy-waits (udelay +
cpu_relax) between tries.

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

diff --git a/rust/kernel/io/poll.rs b/rust/kernel/io/poll.rs
index 613eb25047ef..c7dd0816205f 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,78 @@ pub fn read_poll_timeout<Op, Cond, T>(
         cpu_relax();
     }
 }
+
+/// Polls periodically until a condition is met, an error occurs,
+/// or the attempt limit 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 attempt limit specified by `count` is reached.
+///
+/// # Errors
+///
+/// If `op` returns an error, then that error is returned directly.
+///
+/// If the attempt limit specified by `count` is reached, then
+/// `Err(ETIMEDOUT)` is returned.
+///
+/// # Examples
+///
+/// ```no_run
+/// use kernel::io::{Io, poll::read_poll_count_atomic};
+/// use kernel::time::Delta;
+///
+/// const HW_READY: u16 = 0x01;
+///
+/// fn wait_for_hardware<const SIZE: usize>(io: &Io<SIZE>) -> Result {
+///     match read_poll_count_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),
+///         1000,
+///     ) {
+///         Ok(_) => {
+///             // The hardware is ready. The returned value of the `op` closure
+///             // isn't used.
+///             Ok(())
+///         }
+///         Err(e) => Err(e),
+///     }
+/// }
+/// ```
+pub fn read_poll_count_atomic<Op, Cond, T>(
+    mut op: Op,
+    mut cond: Cond,
+    delay_delta: Delta,
+    count: usize,
+) -> Result<T>
+where
+    Op: FnMut() -> Result<T>,
+    Cond: FnMut(&T) -> bool,
+{
+    for _ in 0..count {
+        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 !delay_delta.is_zero() {
+            udelay(delay_delta);
+        }
+
+        cpu_relax();
+    }
+
+    Err(ETIMEDOUT)
+}
-- 
2.43.0
Re: [PATCH v2 2/2] rust: Add read_poll_count_atomic function
Posted by Danilo Krummrich 3 months, 3 weeks ago
On Tue Oct 21, 2025 at 9:11 AM CEST, FUJITA Tomonori wrote:
> +/// Polls periodically until a condition is met, an error occurs,
> +/// or the attempt limit 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 attempt limit specified by `count` is reached.
> +///
> +/// # Errors
> +///
> +/// If `op` returns an error, then that error is returned directly.
> +///
> +/// If the attempt limit specified by `count` is reached, then
> +/// `Err(ETIMEDOUT)` is returned.
> +///
> +/// # Examples
> +///
> +/// ```no_run
> +/// use kernel::io::{Io, poll::read_poll_count_atomic};
> +/// use kernel::time::Delta;
> +///
> +/// const HW_READY: u16 = 0x01;
> +///
> +/// fn wait_for_hardware<const SIZE: usize>(io: &Io<SIZE>) -> Result {
> +///     match read_poll_count_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),
> +///         1000,
> +///     ) {
> +///         Ok(_) => {
> +///             // The hardware is ready. The returned value of the `op` closure
> +///             // isn't used.
> +///             Ok(())
> +///         }
> +///         Err(e) => Err(e),
> +///     }

Please replace the match statement with map().

	read_poll_count_atomic(
	    ...
	)
	.map(|_| ())

> +/// }
> +/// ```
> +pub fn read_poll_count_atomic<Op, Cond, T>(

I understand why you renamed the function, but read_poll_timeout_atomic() would
still be accurate -- it does perform a timeout in every iteration. Let's keep
the original name please.

> +    mut op: Op,
> +    mut cond: Cond,
> +    delay_delta: Delta,
> +    count: usize,

Maybe retry would be a slightly better fit compared to count. If we want to be a
bit more verbose, I suggest retry_count. :)

> +) -> Result<T>
> +where
> +    Op: FnMut() -> Result<T>,
> +    Cond: FnMut(&T) -> bool,
> +{
> +    for _ in 0..count {
> +        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.

NIT: Just like in read_poll_timeout() I think this comment does not carry much
value, but I'm fine if you want to keep it.

> +            return Ok(val);
> +        }
> +
> +        if !delay_delta.is_zero() {
> +            udelay(delay_delta);
> +        }
> +
> +        cpu_relax();
> +    }
> +
> +    Err(ETIMEDOUT)
> +}
> -- 
> 2.43.0
Re: [PATCH v2 2/2] rust: Add read_poll_count_atomic function
Posted by FUJITA Tomonori 3 months, 2 weeks ago
On Tue, 21 Oct 2025 14:35:34 +0200
"Danilo Krummrich" <dakr@kernel.org> wrote:

>> +pub fn read_poll_count_atomic<Op, Cond, T>(
> 
> I understand why you renamed the function, but read_poll_timeout_atomic() would
> still be accurate -- it does perform a timeout in every iteration. Let's keep
> the original name please.

Ok, I'll revert the name.

>> +    mut op: Op,
>> +    mut cond: Cond,
>> +    delay_delta: Delta,
>> +    count: usize,
> 
> Maybe retry would be a slightly better fit compared to count. If we want to be a
> bit more verbose, I suggest retry_count. :)

Sure, I'll go with 'retry'.
Re: [PATCH v2 2/2] rust: Add read_poll_count_atomic function
Posted by Alice Ryhl 3 months, 3 weeks ago
On Tue, Oct 21, 2025 at 02:35:34PM +0200, Danilo Krummrich wrote:
> On Tue Oct 21, 2025 at 9:11 AM CEST, FUJITA Tomonori wrote:
> > +/// Polls periodically until a condition is met, an error occurs,
> > +/// or the attempt limit 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 attempt limit specified by `count` is reached.
> > +///
> > +/// # Errors
> > +///
> > +/// If `op` returns an error, then that error is returned directly.
> > +///
> > +/// If the attempt limit specified by `count` is reached, then
> > +/// `Err(ETIMEDOUT)` is returned.
> > +///
> > +/// # Examples
> > +///
> > +/// ```no_run
> > +/// use kernel::io::{Io, poll::read_poll_count_atomic};
> > +/// use kernel::time::Delta;
> > +///
> > +/// const HW_READY: u16 = 0x01;
> > +///
> > +/// fn wait_for_hardware<const SIZE: usize>(io: &Io<SIZE>) -> Result {
> > +///     match read_poll_count_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),
> > +///         1000,
> > +///     ) {
> > +///         Ok(_) => {
> > +///             // The hardware is ready. The returned value of the `op` closure
> > +///             // isn't used.
> > +///             Ok(())
> > +///         }
> > +///         Err(e) => Err(e),
> > +///     }
> 
> Please replace the match statement with map().
> 
> 	read_poll_count_atomic(
> 	    ...
> 	)
> 	.map(|_| ())
> 

IMO, this should instead be:

	read_poll_count_atomic(
	    ...
	)?
	Ok(())

Alice
Re: [PATCH v2 2/2] rust: Add read_poll_count_atomic function
Posted by Andreas Hindborg 3 months, 2 weeks ago
"Alice Ryhl" <aliceryhl@google.com> writes:

> On Tue, Oct 21, 2025 at 02:35:34PM +0200, Danilo Krummrich wrote:
>> On Tue Oct 21, 2025 at 9:11 AM CEST, FUJITA Tomonori wrote:
>> > +/// Polls periodically until a condition is met, an error occurs,
>> > +/// or the attempt limit 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 attempt limit specified by `count` is reached.
>> > +///
>> > +/// # Errors
>> > +///
>> > +/// If `op` returns an error, then that error is returned directly.
>> > +///
>> > +/// If the attempt limit specified by `count` is reached, then
>> > +/// `Err(ETIMEDOUT)` is returned.
>> > +///
>> > +/// # Examples
>> > +///
>> > +/// ```no_run
>> > +/// use kernel::io::{Io, poll::read_poll_count_atomic};
>> > +/// use kernel::time::Delta;
>> > +///
>> > +/// const HW_READY: u16 = 0x01;
>> > +///
>> > +/// fn wait_for_hardware<const SIZE: usize>(io: &Io<SIZE>) -> Result {
>> > +///     match read_poll_count_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),
>> > +///         1000,
>> > +///     ) {
>> > +///         Ok(_) => {
>> > +///             // The hardware is ready. The returned value of the `op` closure
>> > +///             // isn't used.
>> > +///             Ok(())
>> > +///         }
>> > +///         Err(e) => Err(e),
>> > +///     }
>>
>> Please replace the match statement with map().
>>
>> 	read_poll_count_atomic(
>> 	    ...
>> 	)
>> 	.map(|_| ())
>>
>
> IMO, this should instead be:
>
> 	read_poll_count_atomic(
> 	    ...
> 	)?
> 	Ok(())

It does not really matter to me. Why do you prefer one to the other?


Best regards,
Andreas Hindborg
Re: [PATCH v2 2/2] rust: Add read_poll_count_atomic function
Posted by Alice Ryhl 3 months, 2 weeks ago
On Fri, Oct 24, 2025 at 10:25:05AM +0200, Andreas Hindborg wrote:
> "Alice Ryhl" <aliceryhl@google.com> writes:
> 
> > On Tue, Oct 21, 2025 at 02:35:34PM +0200, Danilo Krummrich wrote:
> >> On Tue Oct 21, 2025 at 9:11 AM CEST, FUJITA Tomonori wrote:
> >> > +/// Polls periodically until a condition is met, an error occurs,
> >> > +/// or the attempt limit 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 attempt limit specified by `count` is reached.
> >> > +///
> >> > +/// # Errors
> >> > +///
> >> > +/// If `op` returns an error, then that error is returned directly.
> >> > +///
> >> > +/// If the attempt limit specified by `count` is reached, then
> >> > +/// `Err(ETIMEDOUT)` is returned.
> >> > +///
> >> > +/// # Examples
> >> > +///
> >> > +/// ```no_run
> >> > +/// use kernel::io::{Io, poll::read_poll_count_atomic};
> >> > +/// use kernel::time::Delta;
> >> > +///
> >> > +/// const HW_READY: u16 = 0x01;
> >> > +///
> >> > +/// fn wait_for_hardware<const SIZE: usize>(io: &Io<SIZE>) -> Result {
> >> > +///     match read_poll_count_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),
> >> > +///         1000,
> >> > +///     ) {
> >> > +///         Ok(_) => {
> >> > +///             // The hardware is ready. The returned value of the `op` closure
> >> > +///             // isn't used.
> >> > +///             Ok(())
> >> > +///         }
> >> > +///         Err(e) => Err(e),
> >> > +///     }
> >>
> >> Please replace the match statement with map().
> >>
> >> 	read_poll_count_atomic(
> >> 	    ...
> >> 	)
> >> 	.map(|_| ())
> >>
> >
> > IMO, this should instead be:
> >
> > 	read_poll_count_atomic(
> > 	    ...
> > 	)?
> > 	Ok(())
> 
> It does not really matter to me. Why do you prefer one to the other?

I think it's simpler.

Alice
Re: [PATCH v2 2/2] rust: Add read_poll_count_atomic function
Posted by Danilo Krummrich 3 months, 3 weeks ago
On 10/21/25 4:05 PM, Alice Ryhl wrote:
> On Tue, Oct 21, 2025 at 02:35:34PM +0200, Danilo Krummrich wrote:
>> Please replace the match statement with map().
>>
>> 	read_poll_count_atomic(
>> 	    ...
>> 	)
>> 	.map(|_| ())
>>
> 
> IMO, this should instead be:
> 
> 	read_poll_count_atomic(
> 	    ...
> 	)?
> 	Ok(())

I think map() has the advantage that it is a bit more explicit about the fact
that the return value is discarded intentionally.

But I'm fine with either version.
Re: [PATCH v2 2/2] rust: Add read_poll_count_atomic function
Posted by FUJITA Tomonori 3 months, 2 weeks ago
On Tue, 21 Oct 2025 18:02:39 +0200
Danilo Krummrich <dakr@kernel.org> wrote:

> On 10/21/25 4:05 PM, Alice Ryhl wrote:
>> On Tue, Oct 21, 2025 at 02:35:34PM +0200, Danilo Krummrich wrote:
>>> Please replace the match statement with map().
>>>
>>> 	read_poll_count_atomic(
>>> 	    ...
>>> 	)
>>> 	.map(|_| ())
>>>
>> 
>> IMO, this should instead be:
>> 
>> 	read_poll_count_atomic(
>> 	    ...
>> 	)?
>> 	Ok(())
> 
> I think map() has the advantage that it is a bit more explicit about the fact
> that the return value is discarded intentionally.
> 
> But I'm fine with either version.

Then I'll go with 'Ok'.

I'll send to a different patch to update read_poll_wait's example for
the same change.