Commit 349a6425 authored by FUJITA Tomonori's avatar FUJITA Tomonori Committed by Danilo Krummrich
Browse files

rust: Add read_poll_timeout function



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

The C's read_poll_timeout (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 C version uses usleep_range() while the Rust version uses
fsleep(), which uses the best sleep method so it works with spans that
usleep_range() doesn't work nicely with.

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

Reviewed-by: default avatarAndreas Hindborg <a.hindborg@kernel.org>
Reviewed-by: default avatarFiona Behrens <me@kloenk.dev>
Reviewed-by: default avatarAlexandre Courbot <acourbot@nvidia.com>
Tested-by: default avatarAlexandre Courbot <acourbot@nvidia.com>
Reviewed-by: default avatarDaniel Almeida <daniel.almeida@collabora.com>
Tested-by: default avatarDaniel Almeida <daniel.almeida@collabora.com>
Reviewed-by: default avatarAlice Ryhl <aliceryhl@google.com>
Signed-off-by: default avatarFUJITA Tomonori <fujita.tomonori@gmail.com>
Link: https://lore.kernel.org/r/20250821002055.3654160-3-fujita.tomonori@gmail.com


[ Fix a minor typo and add missing backticks. - Danilo ]
Signed-off-by: default avatarDanilo Krummrich <dakr@kernel.org>
parent 842aedc3
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
use crate::{bindings, build_assert, ffi::c_void};

pub mod mem;
pub mod poll;
pub mod resource;

pub use resource::Resource;

rust/kernel/io/poll.rs

0 → 100644
+104 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0

//! IO polling.
//!
//! C header: [`include/linux/iopoll.h`](srctree/include/linux/iopoll.h).

use crate::{
    error::{code::*, Result},
    processor::cpu_relax,
    task::might_sleep,
    time::{delay::fsleep, Delta, Instant, Monotonic},
};

/// 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 waits for a duration specified
/// by `sleep_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.
///
/// This function can only be used in a nonatomic context.
///
/// # 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};
/// use kernel::time::Delta;
///
/// const HW_READY: u16 = 0x01;
///
/// fn wait_for_hardware<const SIZE: usize>(io: &Io<SIZE>) -> Result<()> {
///     match read_poll_timeout(
///         // 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_millis(50),
///         Delta::from_secs(3),
///     ) {
///         Ok(_) => {
///             // The hardware is ready. The returned value of the `op` closure
///             // isn't used.
///             Ok(())
///         }
///         Err(e) => Err(e),
///     }
/// }
/// ```
#[track_caller]
pub fn read_poll_timeout<Op, Cond, T>(
    mut op: Op,
    mut cond: Cond,
    sleep_delta: Delta,
    timeout_delta: Delta,
) -> Result<T>
where
    Op: FnMut() -> Result<T>,
    Cond: FnMut(&T) -> bool,
{
    let start: Instant<Monotonic> = Instant::now();

    // Unlike the C version, we always call `might_sleep()` unconditionally,
    // as conditional calls are error-prone. We clearly separate
    // `read_poll_timeout()` and `read_poll_timeout_atomic()` to aid
    // tools like klint.
    might_sleep();

    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 start.elapsed() > timeout_delta {
            // 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 !sleep_delta.is_zero() {
            fsleep(sleep_delta);
        }

        // `fsleep()` could be a busy-wait loop so we always call `cpu_relax()`.
        cpu_relax();
    }
}