| // SPDX-License-Identifier: GPL-2.0 | |
| //! IO polling. | |
| //! | |
| //! C header: [`include/linux/iopoll.h`](srctree/include/linux/iopoll.h). | |
| use crate::{ | |
| prelude::*, | |
| processor::cpu_relax, | |
| task::might_sleep, | |
| time::{ | |
| delay::{ | |
| fsleep, | |
| udelay, // | |
| }, | |
| 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, | |
| /// Mmio, | |
| /// poll::read_poll_timeout, // | |
| /// }; | |
| /// use kernel::time::Delta; | |
| /// | |
| /// const HW_READY: u16 = 0x01; | |
| /// | |
| /// fn wait_for_hardware<const SIZE: usize>(io: &Mmio<SIZE>) -> Result { | |
| /// 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(()) | |
| /// } | |
| /// ``` | |
| #[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(); | |
| } | |
| } | |
| /// 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 `retry` is reached. | |
| /// | |
| /// # Errors | |
| /// | |
| /// If `op` returns an error, then that error is returned directly. | |
| /// | |
| /// If the attempt limit specified by `retry` is reached, then | |
| /// `Err(ETIMEDOUT)` is returned. | |
| /// | |
| /// # Examples | |
| /// | |
| /// ```no_run | |
| /// use kernel::io::{ | |
| /// Io, | |
| /// Mmio, | |
| /// poll::read_poll_timeout_atomic, // | |
| /// }; | |
| /// use kernel::time::Delta; | |
| /// | |
| /// const HW_READY: u16 = 0x01; | |
| /// | |
| /// fn wait_for_hardware<const SIZE: usize>(io: &Mmio<SIZE>) -> Result { | |
| /// 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), | |
| /// 1000, | |
| /// )?; | |
| /// Ok(()) | |
| /// } | |
| /// ``` | |
| pub fn read_poll_timeout_atomic<Op, Cond, T>( | |
| mut op: Op, | |
| mut cond: Cond, | |
| delay_delta: Delta, | |
| retry: usize, | |
| ) -> Result<T> | |
| where | |
| Op: FnMut() -> Result<T>, | |
| Cond: FnMut(&T) -> bool, | |
| { | |
| for _ in 0..retry { | |
| let val = op()?; | |
| if cond(&val) { | |
| return Ok(val); | |
| } | |
| if !delay_delta.is_zero() { | |
| udelay(delay_delta); | |
| } | |
| cpu_relax(); | |
| } | |
| Err(ETIMEDOUT) | |
| } |