diff mbox series

[net-next,v3,7/8] rust: Add read_poll_timeout functions

Message ID 20241016035214.2229-8-fujita.tomonori@gmail.com (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series rust: Add IO polling | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 5 this patch: 5
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers warning 2 maintainers not CCed: wedsonaf@gmail.com boqun.feng@gmail.com
netdev/build_clang success Errors and warnings before: 4 this patch: 4
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn fail Errors and warnings before: 40 this patch: 42
netdev/checkpatch warning WARNING: Duplicate signature WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 92 exceeds 80 columns
netdev/build_clang_rust fail Link
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

FUJITA Tomonori Oct. 16, 2024, 3:52 a.m. UTC
Add read_poll_timeout functions which poll periodically until a
condition is met or a timeout is reached.

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.

Unlike the C version, __might_sleep() is used instead of might_sleep()
to show proper debug info; the file name and line
number. might_resched() could be added to match what the C version
does but this function works without it.

For the proper debug info, readx_poll_timeout() is implemented as a
macro.

readx_poll_timeout() can only be used in a nonatomic context. This
requirement is not checked by these abstractions, but it is
intended that klint [1] or a similar tool will be used to check it
in the future.

Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
Link: https://rust-for-linux.com/klint [1]

Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
---
 rust/helpers/helpers.c |  1 +
 rust/helpers/kernel.c  | 13 +++++++
 rust/kernel/error.rs   |  1 +
 rust/kernel/io.rs      |  5 +++
 rust/kernel/io/poll.rs | 84 ++++++++++++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs     |  1 +
 6 files changed, 105 insertions(+)
 create mode 100644 rust/helpers/kernel.c
 create mode 100644 rust/kernel/io.rs
 create mode 100644 rust/kernel/io/poll.rs

Comments

Alice Ryhl Oct. 16, 2024, 8:37 a.m. UTC | #1
On Wed, Oct 16, 2024 at 5:54 AM FUJITA Tomonori
<fujita.tomonori@gmail.com> wrote:
>
> Add read_poll_timeout functions which poll periodically until a
> condition is met or a timeout is reached.
>
> 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.
>
> Unlike the C version, __might_sleep() is used instead of might_sleep()
> to show proper debug info; the file name and line
> number. might_resched() could be added to match what the C version
> does but this function works without it.
>
> For the proper debug info, readx_poll_timeout() is implemented as a
> macro.
>
> readx_poll_timeout() can only be used in a nonatomic context. This
> requirement is not checked by these abstractions, but it is
> intended that klint [1] or a similar tool will be used to check it
> in the future.
>
> Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
> Link: https://rust-for-linux.com/klint [1]
>
> Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>

Duplicated SOB? This should just be:

Link: https://rust-for-linux.com/klint [1]
Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>

> +/// Polls periodically until a condition is met or a timeout is reached.
> +///
> +/// Public but hidden since it should only be used from public macros.
> +#[doc(hidden)]
> +pub fn read_poll_timeout<Op, Cond, T: Copy>(
> +    mut op: Op,
> +    cond: Cond,
> +    sleep_delta: Delta,
> +    timeout_delta: Delta,
> +    sleep_before_read: bool,
> +) -> Result<T>
> +where
> +    Op: FnMut() -> Result<T>,
> +    Cond: Fn(T) -> bool,
> +{
> +    let timeout = Ktime::ktime_get() + timeout_delta;
> +    let sleep = !sleep_delta.is_zero();
> +
> +    if sleep_before_read && sleep {
> +        fsleep(sleep_delta);
> +    }

You always pass `false` for `sleep_before_read` so perhaps just remove
this and the argument entirely?

> +        if cond(val) {
> +            break val;
> +        }

This breaks out to another cond(val) check below. Perhaps just `return
Ok(val)` here to avoid the double condition check?

> +        if !timeout_delta.is_zero() && Ktime::ktime_get() > timeout {
> +            break op()?;
> +        }

Shouldn't you just return `Err(ETIMEDOUT)` here? I don't think you're
supposed to call `op` twice without a sleep in between.

> +        if sleep {
> +            fsleep(sleep_delta);
> +        }
> +        // SAFETY: FFI call.
> +        unsafe { bindings::cpu_relax() }

Should cpu_relax() be in an else branch? Also, please add a safe
wrapper function around cpu_relax.

> +macro_rules! readx_poll_timeout {
> +    ($op:expr, $cond:expr, $sleep_delta:expr, $timeout_delta:expr) => {{
> +        #[cfg(CONFIG_DEBUG_ATOMIC_SLEEP)]
> +        if !$sleep_delta.is_zero() {
> +            // SAFETY: FFI call.
> +            unsafe {
> +                $crate::bindings::__might_sleep(
> +                    ::core::file!().as_ptr() as *const i8,
> +                    ::core::line!() as i32,
> +                )
> +            }
> +        }

It could be nice to introduce a might_sleep macro that does this
internally? Then we can reuse this logic in other places.

Alice
Alice Ryhl Oct. 16, 2024, 8:45 a.m. UTC | #2
On Wed, Oct 16, 2024 at 5:54 AM FUJITA Tomonori
<fujita.tomonori@gmail.com> wrote:
> +/// Polls periodically until a condition is met or a timeout is reached.
> +///
> +/// `op` is called repeatedly until `cond` returns `true` or the timeout is
> +///  reached. The return value of `op` is passed to `cond`.
> +///
> +/// `sleep_delta` is the duration to sleep between calls to `op`.
> +/// If `sleep_delta` is less than one microsecond, the function will busy-wait.
> +///
> +/// `timeout_delta` is the maximum time to wait for `cond` to return `true`.
> +///
> +/// This macro can only be used in a nonatomic context.
> +#[macro_export]
> +macro_rules! readx_poll_timeout {
> +    ($op:expr, $cond:expr, $sleep_delta:expr, $timeout_delta:expr) => {{
> +        #[cfg(CONFIG_DEBUG_ATOMIC_SLEEP)]
> +        if !$sleep_delta.is_zero() {
> +            // SAFETY: FFI call.
> +            unsafe {
> +                $crate::bindings::__might_sleep(
> +                    ::core::file!().as_ptr() as *const i8,
> +                    ::core::line!() as i32,
> +                )
> +            }
> +        }

I wonder if we can use #[track_caller] and
core::panic::Location::caller [1] to do this without having to use a
macro? I don't know whether it would work, but if it does, that would
be super cool.

#[track_caller]
fn might_sleep() {
    let location = core::panic::Location::caller();
    // SAFETY: FFI call.
    unsafe {
        $crate::bindings::__might_sleep(
            location.file().as_char_ptr(),
            location.line() as i32,
        )
    }
}

Alice

[1]: https://doc.rust-lang.org/nightly/core/panic/struct.Location.html#method.caller
Alice Ryhl Oct. 16, 2024, 8:52 a.m. UTC | #3
On Wed, Oct 16, 2024 at 10:45 AM Alice Ryhl <aliceryhl@google.com> wrote:
>
> On Wed, Oct 16, 2024 at 5:54 AM FUJITA Tomonori
> <fujita.tomonori@gmail.com> wrote:
> > +/// Polls periodically until a condition is met or a timeout is reached.
> > +///
> > +/// `op` is called repeatedly until `cond` returns `true` or the timeout is
> > +///  reached. The return value of `op` is passed to `cond`.
> > +///
> > +/// `sleep_delta` is the duration to sleep between calls to `op`.
> > +/// If `sleep_delta` is less than one microsecond, the function will busy-wait.
> > +///
> > +/// `timeout_delta` is the maximum time to wait for `cond` to return `true`.
> > +///
> > +/// This macro can only be used in a nonatomic context.
> > +#[macro_export]
> > +macro_rules! readx_poll_timeout {
> > +    ($op:expr, $cond:expr, $sleep_delta:expr, $timeout_delta:expr) => {{
> > +        #[cfg(CONFIG_DEBUG_ATOMIC_SLEEP)]
> > +        if !$sleep_delta.is_zero() {
> > +            // SAFETY: FFI call.
> > +            unsafe {
> > +                $crate::bindings::__might_sleep(
> > +                    ::core::file!().as_ptr() as *const i8,
> > +                    ::core::line!() as i32,
> > +                )
> > +            }
> > +        }
>
> I wonder if we can use #[track_caller] and
> core::panic::Location::caller [1] to do this without having to use a
> macro? I don't know whether it would work, but if it does, that would
> be super cool.
>
> #[track_caller]
> fn might_sleep() {
>     let location = core::panic::Location::caller();
>     // SAFETY: FFI call.
>     unsafe {
>         $crate::bindings::__might_sleep(
>             location.file().as_char_ptr(),
>             location.line() as i32,
>         )
>     }
> }

Actually, this raises a problem ... core::panic::Location doesn't give
us a nul-terminated string, so we probably can't pass it to
`__might_sleep`. The thing is, `::core::file!()` doesn't give us a
nul-terminated string either, so this code is probably incorrect
as-is.

Alice
Miguel Ojeda Oct. 16, 2024, 11:05 a.m. UTC | #4
On Wed, Oct 16, 2024 at 10:52 AM Alice Ryhl <aliceryhl@google.com> wrote:
>
> `__might_sleep`. The thing is, `::core::file!()` doesn't give us a
> nul-terminated string either, so this code is probably incorrect
> as-is.

Yeah, elsewhere we use `c_str!`. It would be nice to get
`core::c_file!` too, though that one is less critical since we can do
it ourselves. Perhaps we should also consider using Clippy's
`disallowed_macros` too if we don't expect people to often need the
normal one. Added to our list:

    https://github.com/Rust-for-Linux/linux/issues/514

Cheers,
Miguel
FUJITA Tomonori Oct. 18, 2024, 8:10 a.m. UTC | #5
On Wed, 16 Oct 2024 10:52:17 +0200
Alice Ryhl <aliceryhl@google.com> wrote:

> On Wed, Oct 16, 2024 at 10:45 AM Alice Ryhl <aliceryhl@google.com> wrote:
>>
>> On Wed, Oct 16, 2024 at 5:54 AM FUJITA Tomonori
>> <fujita.tomonori@gmail.com> wrote:
>> > +/// Polls periodically until a condition is met or a timeout is reached.
>> > +///
>> > +/// `op` is called repeatedly until `cond` returns `true` or the timeout is
>> > +///  reached. The return value of `op` is passed to `cond`.
>> > +///
>> > +/// `sleep_delta` is the duration to sleep between calls to `op`.
>> > +/// If `sleep_delta` is less than one microsecond, the function will busy-wait.
>> > +///
>> > +/// `timeout_delta` is the maximum time to wait for `cond` to return `true`.
>> > +///
>> > +/// This macro can only be used in a nonatomic context.
>> > +#[macro_export]
>> > +macro_rules! readx_poll_timeout {
>> > +    ($op:expr, $cond:expr, $sleep_delta:expr, $timeout_delta:expr) => {{
>> > +        #[cfg(CONFIG_DEBUG_ATOMIC_SLEEP)]
>> > +        if !$sleep_delta.is_zero() {
>> > +            // SAFETY: FFI call.
>> > +            unsafe {
>> > +                $crate::bindings::__might_sleep(
>> > +                    ::core::file!().as_ptr() as *const i8,
>> > +                    ::core::line!() as i32,
>> > +                )
>> > +            }
>> > +        }
>>
>> I wonder if we can use #[track_caller] and
>> core::panic::Location::caller [1] to do this without having to use a
>> macro? I don't know whether it would work, but if it does, that would
>> be super cool.

Seems it works, no need to use macro. Thanks a lot!

>> #[track_caller]
>> fn might_sleep() {
>>     let location = core::panic::Location::caller();
>>     // SAFETY: FFI call.
>>     unsafe {
>>         $crate::bindings::__might_sleep(
>>             location.file().as_char_ptr(),
>>             location.line() as i32,
>>         )
>>     }
>> }
> 
> Actually, this raises a problem ... core::panic::Location doesn't give
> us a nul-terminated string, so we probably can't pass it to
> `__might_sleep`. The thing is, `::core::file!()` doesn't give us a
> nul-terminated string either, so this code is probably incorrect
> as-is.

Ah, what's the recommended way to get a null-terminated string from
&str?
Alice Ryhl Oct. 18, 2024, 8:30 a.m. UTC | #6
On Fri, Oct 18, 2024 at 10:10 AM FUJITA Tomonori
<fujita.tomonori@gmail.com> wrote:
>
> On Wed, 16 Oct 2024 10:52:17 +0200
> Alice Ryhl <aliceryhl@google.com> wrote:
>
> > On Wed, Oct 16, 2024 at 10:45 AM Alice Ryhl <aliceryhl@google.com> wrote:
> >>
> >> On Wed, Oct 16, 2024 at 5:54 AM FUJITA Tomonori
> >> <fujita.tomonori@gmail.com> wrote:
> >> > +/// Polls periodically until a condition is met or a timeout is reached.
> >> > +///
> >> > +/// `op` is called repeatedly until `cond` returns `true` or the timeout is
> >> > +///  reached. The return value of `op` is passed to `cond`.
> >> > +///
> >> > +/// `sleep_delta` is the duration to sleep between calls to `op`.
> >> > +/// If `sleep_delta` is less than one microsecond, the function will busy-wait.
> >> > +///
> >> > +/// `timeout_delta` is the maximum time to wait for `cond` to return `true`.
> >> > +///
> >> > +/// This macro can only be used in a nonatomic context.
> >> > +#[macro_export]
> >> > +macro_rules! readx_poll_timeout {
> >> > +    ($op:expr, $cond:expr, $sleep_delta:expr, $timeout_delta:expr) => {{
> >> > +        #[cfg(CONFIG_DEBUG_ATOMIC_SLEEP)]
> >> > +        if !$sleep_delta.is_zero() {
> >> > +            // SAFETY: FFI call.
> >> > +            unsafe {
> >> > +                $crate::bindings::__might_sleep(
> >> > +                    ::core::file!().as_ptr() as *const i8,
> >> > +                    ::core::line!() as i32,
> >> > +                )
> >> > +            }
> >> > +        }
> >>
> >> I wonder if we can use #[track_caller] and
> >> core::panic::Location::caller [1] to do this without having to use a
> >> macro? I don't know whether it would work, but if it does, that would
> >> be super cool.
>
> Seems it works, no need to use macro. Thanks a lot!
>
> >> #[track_caller]
> >> fn might_sleep() {
> >>     let location = core::panic::Location::caller();
> >>     // SAFETY: FFI call.
> >>     unsafe {
> >>         $crate::bindings::__might_sleep(
> >>             location.file().as_char_ptr(),
> >>             location.line() as i32,
> >>         )
> >>     }
> >> }
> >
> > Actually, this raises a problem ... core::panic::Location doesn't give
> > us a nul-terminated string, so we probably can't pass it to
> > `__might_sleep`. The thing is, `::core::file!()` doesn't give us a
> > nul-terminated string either, so this code is probably incorrect
> > as-is.
>
> Ah, what's the recommended way to get a null-terminated string from
> &str?

In this case, you should be able to use the `c_str!` macro.

`kernel::c_str!(core::file!()).as_char_ptr()`

Alice
Andrew Lunn Oct. 18, 2024, 2:15 p.m. UTC | #7
> > Ah, what's the recommended way to get a null-terminated string from
> > &str?
> 
> In this case, you should be able to use the `c_str!` macro.
> 
> `kernel::c_str!(core::file!()).as_char_ptr()`

Does this allocate memory? In this case, that would be O.K, but at
some point i expect somebody is going to want the atomic version of
this poll helper. You then need to pass additional flags to kalloc()
if you call it in atomic context.

	Andrew
FUJITA Tomonori Oct. 18, 2024, 2:16 p.m. UTC | #8
On Wed, 16 Oct 2024 10:37:46 +0200
Alice Ryhl <aliceryhl@google.com> wrote:

> On Wed, Oct 16, 2024 at 5:54 AM FUJITA Tomonori
> <fujita.tomonori@gmail.com> wrote:
>>
>> Add read_poll_timeout functions which poll periodically until a
>> condition is met or a timeout is reached.
>>
>> 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.
>>
>> Unlike the C version, __might_sleep() is used instead of might_sleep()
>> to show proper debug info; the file name and line
>> number. might_resched() could be added to match what the C version
>> does but this function works without it.
>>
>> For the proper debug info, readx_poll_timeout() is implemented as a
>> macro.
>>
>> readx_poll_timeout() can only be used in a nonatomic context. This
>> requirement is not checked by these abstractions, but it is
>> intended that klint [1] or a similar tool will be used to check it
>> in the future.
>>
>> Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
>> Link: https://rust-for-linux.com/klint [1]
>>
>> Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>
> 
> Duplicated SOB? This should just be:
> 
> Link: https://rust-for-linux.com/klint [1]
> Signed-off-by: FUJITA Tomonori <fujita.tomonori@gmail.com>

Oops, I'll fix.

>> +/// Polls periodically until a condition is met or a timeout is reached.
>> +///
>> +/// Public but hidden since it should only be used from public macros.
>> +#[doc(hidden)]
>> +pub fn read_poll_timeout<Op, Cond, T: Copy>(
>> +    mut op: Op,
>> +    cond: Cond,
>> +    sleep_delta: Delta,
>> +    timeout_delta: Delta,
>> +    sleep_before_read: bool,
>> +) -> Result<T>
>> +where
>> +    Op: FnMut() -> Result<T>,
>> +    Cond: Fn(T) -> bool,
>> +{
>> +    let timeout = Ktime::ktime_get() + timeout_delta;
>> +    let sleep = !sleep_delta.is_zero();
>> +
>> +    if sleep_before_read && sleep {
>> +        fsleep(sleep_delta);
>> +    }
> 
> You always pass `false` for `sleep_before_read` so perhaps just remove
> this and the argument entirely?

Most of users of C's this function use false buf some use true. It
would be better to provide this option?

Would be better to provide only read_poll_timeout() which takes the
sleep_before_read argument?; No readx_poll_timeout wrapper.

>> +        if cond(val) {
>> +            break val;
>> +        }
> 
> This breaks out to another cond(val) check below. Perhaps just `return
> Ok(val)` here to avoid the double condition check?

I think that that's how the C version works. But `return Ok(val)` here
is fine, I guess.

>> +        if !timeout_delta.is_zero() && Ktime::ktime_get() > timeout {
>> +            break op()?;
>> +        }
> 
> Shouldn't you just return `Err(ETIMEDOUT)` here? I don't think you're
> supposed to call `op` twice without a sleep in between.

I think that it's how the C version works.

>> +        if sleep {
>> +            fsleep(sleep_delta);
>> +        }
>> +        // SAFETY: FFI call.
>> +        unsafe { bindings::cpu_relax() }
> 
> Should cpu_relax() be in an else branch?

I think that we could. I'll remove if you prefer. The C version always
calls cpu_relax(). I think that the following commit explains why:

commit b407460ee99033503993ac7437d593451fcdfe44
Author: Geert Uytterhoeven <geert+renesas@glider.be>
Date:   Fri Jun 2 10:50:36 2023 +0200

    iopoll: Call cpu_relax() in busy loops


> Also, please add a safe wrapper function around cpu_relax.

Sure, which file do you think is best to add the wrapper to?

rust/kernel/processor.rs

?

>> +macro_rules! readx_poll_timeout {
>> +    ($op:expr, $cond:expr, $sleep_delta:expr, $timeout_delta:expr) => {{
>> +        #[cfg(CONFIG_DEBUG_ATOMIC_SLEEP)]
>> +        if !$sleep_delta.is_zero() {
>> +            // SAFETY: FFI call.
>> +            unsafe {
>> +                $crate::bindings::__might_sleep(
>> +                    ::core::file!().as_ptr() as *const i8,
>> +                    ::core::line!() as i32,
>> +                )
>> +            }
>> +        }
> 
> It could be nice to introduce a might_sleep macro that does this
> internally? Then we can reuse this logic in other places.

I think that with #[track_caller], we can use a normal function
instead of a macro.

Using might_sleep name for a wrapper of __might_sleep is confusing
since the C side has also might_sleep. __foo function name is
acceptable?
Andrew Lunn Oct. 18, 2024, 2:21 p.m. UTC | #9
> +            // SAFETY: FFI call.
> +            unsafe {
> +                $crate::bindings::__might_sleep(
> +                    ::core::file!().as_ptr() as *const i8,
> +                    ::core::line!() as i32,
> +                )

Can this be pulled out into an easy to share macro? I expect to see
this to be scattered in a number of files, so making it easy to use
would be good.

	Andrew
Miguel Ojeda Oct. 18, 2024, 2:38 p.m. UTC | #10
On Fri, Oct 18, 2024 at 4:15 PM Andrew Lunn <andrew@lunn.ch> wrote:
>
> Does this allocate memory? In this case, that would be O.K, but at

No, it does not -- it is all done at compile-time.

Cheers,
Miguel
diff mbox series

Patch

diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index c274546bcf78..f9569ff1717e 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -12,6 +12,7 @@ 
 #include "build_assert.c"
 #include "build_bug.c"
 #include "err.c"
+#include "kernel.c"
 #include "kunit.c"
 #include "mutex.c"
 #include "page.c"
diff --git a/rust/helpers/kernel.c b/rust/helpers/kernel.c
new file mode 100644
index 000000000000..da847059260b
--- /dev/null
+++ b/rust/helpers/kernel.c
@@ -0,0 +1,13 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/kernel.h>
+
+void rust_helper_cpu_relax(void)
+{
+	cpu_relax();
+}
+
+void rust_helper___might_sleep(const char *file, int line)
+{
+	__might_sleep(file, line);
+}
diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
index 6f1587a2524e..d571b9587ed6 100644
--- a/rust/kernel/error.rs
+++ b/rust/kernel/error.rs
@@ -58,6 +58,7 @@  macro_rules! declare_err {
     declare_err!(EPIPE, "Broken pipe.");
     declare_err!(EDOM, "Math argument out of domain of func.");
     declare_err!(ERANGE, "Math result not representable.");
+    declare_err!(ETIMEDOUT, "Connection timed out.");
     declare_err!(ERESTARTSYS, "Restart the system call.");
     declare_err!(ERESTARTNOINTR, "System call was interrupted by a signal and will be restarted.");
     declare_err!(ERESTARTNOHAND, "Restart if no handler.");
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
new file mode 100644
index 000000000000..033f3c4e4adf
--- /dev/null
+++ b/rust/kernel/io.rs
@@ -0,0 +1,5 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+//! Input and Output.
+
+pub mod poll;
diff --git a/rust/kernel/io/poll.rs b/rust/kernel/io/poll.rs
new file mode 100644
index 000000000000..d4bb791f665b
--- /dev/null
+++ b/rust/kernel/io/poll.rs
@@ -0,0 +1,84 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+//! IO polling.
+//!
+//! C header: [`include/linux/iopoll.h`](srctree/include/linux/iopoll.h).
+
+use crate::{
+    bindings,
+    error::{code::*, Result},
+    time::{delay::fsleep, Delta, Ktime},
+};
+
+/// Polls periodically until a condition is met or a timeout is reached.
+///
+/// Public but hidden since it should only be used from public macros.
+#[doc(hidden)]
+pub fn read_poll_timeout<Op, Cond, T: Copy>(
+    mut op: Op,
+    cond: Cond,
+    sleep_delta: Delta,
+    timeout_delta: Delta,
+    sleep_before_read: bool,
+) -> Result<T>
+where
+    Op: FnMut() -> Result<T>,
+    Cond: Fn(T) -> bool,
+{
+    let timeout = Ktime::ktime_get() + timeout_delta;
+    let sleep = !sleep_delta.is_zero();
+
+    if sleep_before_read && sleep {
+        fsleep(sleep_delta);
+    }
+
+    let val = loop {
+        let val = op()?;
+        if cond(val) {
+            break val;
+        }
+        if !timeout_delta.is_zero() && Ktime::ktime_get() > timeout {
+            break op()?;
+        }
+        if sleep {
+            fsleep(sleep_delta);
+        }
+        // SAFETY: FFI call.
+        unsafe { bindings::cpu_relax() }
+    };
+
+    if cond(val) {
+        Ok(val)
+    } else {
+        Err(ETIMEDOUT)
+    }
+}
+
+/// Polls periodically until a condition is met or a timeout is reached.
+///
+/// `op` is called repeatedly until `cond` returns `true` or the timeout is
+///  reached. The return value of `op` is passed to `cond`.
+///
+/// `sleep_delta` is the duration to sleep between calls to `op`.
+/// If `sleep_delta` is less than one microsecond, the function will busy-wait.
+///
+/// `timeout_delta` is the maximum time to wait for `cond` to return `true`.
+///
+/// This macro can only be used in a nonatomic context.
+#[macro_export]
+macro_rules! readx_poll_timeout {
+    ($op:expr, $cond:expr, $sleep_delta:expr, $timeout_delta:expr) => {{
+        #[cfg(CONFIG_DEBUG_ATOMIC_SLEEP)]
+        if !$sleep_delta.is_zero() {
+            // SAFETY: FFI call.
+            unsafe {
+                $crate::bindings::__might_sleep(
+                    ::core::file!().as_ptr() as *const i8,
+                    ::core::line!() as i32,
+                )
+            }
+        }
+
+        $crate::io::poll::read_poll_timeout($op, $cond, $sleep_delta, $timeout_delta, false)
+    }};
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index b5f4b3ce6b48..d5fd6aeb24ca 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -35,6 +35,7 @@ 
 #[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
 pub mod firmware;
 pub mod init;
+pub mod io;
 pub mod ioctl;
 #[cfg(CONFIG_KUNIT)]
 pub mod kunit;