Message ID | 20250125101854.112261-8-fujita.tomonori@gmail.com (mailing list archive) |
---|---|
State | Not Applicable |
Headers | show |
Series | rust: Add IO polling | expand |
On Sat, 25 Jan 2025 19:18:52 +0900 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. > > 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. > > 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. > > The sleep_before_read argument isn't supported since there is no user > for now. It's rarely used in the C version. > > read_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. > > 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/cpu.rs | 13 +++++++ > rust/kernel/error.rs | 1 + > rust/kernel/io.rs | 5 +++ > rust/kernel/io/poll.rs | 79 ++++++++++++++++++++++++++++++++++++++++++ > rust/kernel/lib.rs | 2 ++ > 7 files changed, 114 insertions(+) > create mode 100644 rust/helpers/kernel.c > create mode 100644 rust/kernel/cpu.rs > create mode 100644 rust/kernel/io.rs > create mode 100644 rust/kernel/io/poll.rs > > diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c > index d16aeda7a558..7ab71a6d4603 100644 > --- a/rust/helpers/helpers.c > +++ b/rust/helpers/helpers.c > @@ -13,6 +13,7 @@ > #include "build_bug.c" > #include "cred.c" > #include "err.c" > +#include "kernel.c" > #include "fs.c" > #include "jump_label.c" > #include "kunit.c" > diff --git a/rust/helpers/kernel.c b/rust/helpers/kernel.c > new file mode 100644 > index 000000000000..9dff28f4618e > --- /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_precision(const char *file, int len, int line) > +{ > + __might_sleep_precision(file, len, line); > +} > diff --git a/rust/kernel/cpu.rs b/rust/kernel/cpu.rs > new file mode 100644 > index 000000000000..eeeff4be84fa > --- /dev/null > +++ b/rust/kernel/cpu.rs > @@ -0,0 +1,13 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +//! Processor related primitives. > +//! > +//! C header: [`include/linux/processor.h`](srctree/include/linux/processor.h). > + > +/// Lower CPU power consumption or yield to a hyperthreaded twin processor. > +/// > +/// It also happens to serve as a compiler barrier. > +pub fn cpu_relax() { > + // SAFETY: FFI call. > + unsafe { bindings::cpu_relax() } > +} > diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs > index f6ecf09cb65f..8858eb13b3df 100644 > --- a/rust/kernel/error.rs > +++ b/rust/kernel/error.rs > @@ -64,6 +64,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..7a503cf643a1 > --- /dev/null > +++ b/rust/kernel/io/poll.rs > @@ -0,0 +1,79 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +//! IO polling. > +//! > +//! C header: [`include/linux/iopoll.h`](srctree/include/linux/iopoll.h). > + > +use crate::{ > + cpu::cpu_relax, > + error::{code::*, Result}, > + time::{delay::fsleep, Delta, Instant}, > +}; > + > +use core::panic::Location; > + > +/// Polls periodically until a condition is met or a timeout is reached. > +/// > +/// Public but hidden since it should only be used from public macros. > +/// > +/// ```rust > +/// use kernel::io::poll::read_poll_timeout; > +/// use kernel::time::Delta; > +/// use kernel::sync::{SpinLock, new_spinlock}; > +/// > +/// let lock = KBox::pin_init(new_spinlock!(()), kernel::alloc::flags::GFP_KERNEL)?; > +/// let g = lock.lock(); > +/// read_poll_timeout(|| Ok(()), |()| true, Delta::from_micros(42), Delta::from_micros(42)); > +/// drop(g); > +/// > +/// # Ok::<(), Error>(()) > +/// ``` > +#[track_caller] > +pub fn read_poll_timeout<Op, Cond, T: Copy>( > + 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::now(); > + let sleep = !sleep_delta.is_zero(); > + let timeout = !timeout_delta.is_zero(); > + > + if sleep { > + might_sleep(Location::caller()); > + } > + > + 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 timeout && start.elapsed() > timeout_delta { Re-reading this again I wonder if this is the desired behaviour? Maybe a timeout of 0 should mean check-once instead of no timeout. The special-casing of 0 makes sense in C but in Rust we should use `None` to mean it instead? For `sleep_delta` it's fine to not use `Option`. > + // 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 { > + fsleep(sleep_delta); > + } > + // fsleep() could be busy-wait loop so we always call cpu_relax(). > + cpu_relax(); > + } > +} > + > +fn might_sleep(loc: &Location<'_>) { > + // SAFETY: FFI call. > + unsafe { > + crate::bindings::__might_sleep_precision( > + loc.file().as_ptr().cast(), > + loc.file().len() as i32, > + loc.line() as i32, > + ) > + } > +} > diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs > index 545d1170ee63..c477701b2efa 100644 > --- a/rust/kernel/lib.rs > +++ b/rust/kernel/lib.rs > @@ -35,6 +35,7 @@ > pub mod block; > #[doc(hidden)] > pub mod build_assert; > +pub mod cpu; > pub mod cred; > pub mod device; > pub mod error; > @@ -42,6 +43,7 @@ > pub mod firmware; > pub mod fs; > pub mod init; > +pub mod io; > pub mod ioctl; > pub mod jump_label; > #[cfg(CONFIG_KUNIT)]
On Mon, 27 Jan 2025 11:46:46 +0800 Gary Guo <gary@garyguo.net> wrote: >> +#[track_caller] >> +pub fn read_poll_timeout<Op, Cond, T: Copy>( >> + 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::now(); >> + let sleep = !sleep_delta.is_zero(); >> + let timeout = !timeout_delta.is_zero(); >> + >> + if sleep { >> + might_sleep(Location::caller()); >> + } >> + >> + 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 timeout && start.elapsed() > timeout_delta { > > Re-reading this again I wonder if this is the desired behaviour? Maybe > a timeout of 0 should mean check-once instead of no timeout. The > special-casing of 0 makes sense in C but in Rust we should use `None` > to mean it instead? It's the behavior of the C version; the comment of this function says: * @timeout_us: Timeout in us, 0 means never timeout You meant that waiting for a condition without a timeout is generally a bad idea? If so, can we simply return EINVAL for zero Delta?
On Mon, 27 Jan 2025 15:31:47 +0900 (JST) FUJITA Tomonori <fujita.tomonori@gmail.com> wrote: > On Mon, 27 Jan 2025 11:46:46 +0800 > Gary Guo <gary@garyguo.net> wrote: > > >> +#[track_caller] > >> +pub fn read_poll_timeout<Op, Cond, T: Copy>( > >> + 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::now(); > >> + let sleep = !sleep_delta.is_zero(); > >> + let timeout = !timeout_delta.is_zero(); > >> + > >> + if sleep { > >> + might_sleep(Location::caller()); > >> + } > >> + > >> + 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 timeout && start.elapsed() > timeout_delta { > > > > Re-reading this again I wonder if this is the desired behaviour? Maybe > > a timeout of 0 should mean check-once instead of no timeout. The > > special-casing of 0 makes sense in C but in Rust we should use `None` > > to mean it instead? > > It's the behavior of the C version; the comment of this function says: > > * @timeout_us: Timeout in us, 0 means never timeout > > You meant that waiting for a condition without a timeout is generally > a bad idea? If so, can we simply return EINVAL for zero Delta? > No, I think we should still keep the ability to represent indefinite wait (no timeout) but we should use `None` to represent this rather than `Delta::ZERO`. I know that we use 0 to mean indefinite wait in C, I am saying that it's not the most intuitive way to represent in Rust. Intuitively, a timeout of 0 should be closer to a timeout of 1 and thus should mean "return with ETIMEDOUT immedidately" rather than "wait forever". In C since we don't have a very good sum type support, so we special case 0 to be the special value to represent indefinite wait, but I don't think we need to repeat this in Rust.
On Tue, 28 Jan 2025 08:49:37 +0800 Gary Guo <gary@garyguo.net> wrote: > On Mon, 27 Jan 2025 15:31:47 +0900 (JST) > FUJITA Tomonori <fujita.tomonori@gmail.com> wrote: > >> On Mon, 27 Jan 2025 11:46:46 +0800 >> Gary Guo <gary@garyguo.net> wrote: >> >> >> +#[track_caller] >> >> +pub fn read_poll_timeout<Op, Cond, T: Copy>( >> >> + 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::now(); >> >> + let sleep = !sleep_delta.is_zero(); >> >> + let timeout = !timeout_delta.is_zero(); >> >> + >> >> + if sleep { >> >> + might_sleep(Location::caller()); >> >> + } >> >> + >> >> + 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 timeout && start.elapsed() > timeout_delta { >> > >> > Re-reading this again I wonder if this is the desired behaviour? Maybe >> > a timeout of 0 should mean check-once instead of no timeout. The >> > special-casing of 0 makes sense in C but in Rust we should use `None` >> > to mean it instead? >> >> It's the behavior of the C version; the comment of this function says: >> >> * @timeout_us: Timeout in us, 0 means never timeout >> >> You meant that waiting for a condition without a timeout is generally >> a bad idea? If so, can we simply return EINVAL for zero Delta? >> > > No, I think we should still keep the ability to represent indefinite > wait (no timeout) but we should use `None` to represent this rather > than `Delta::ZERO`. > > I know that we use 0 to mean indefinite wait in C, I am saying that > it's not the most intuitive way to represent in Rust. > > Intuitively, a timeout of 0 should be closer to a timeout of 1 and thus > should mean "return with ETIMEDOUT immedidately" rather than "wait > forever". > > In C since we don't have a very good sum type support, so we > special case 0 to be the special value to represent indefinite wait, > but I don't think we need to repeat this in Rust. Understood, thanks. How about the following code? +#[track_caller] +pub fn read_poll_timeout<Op, Cond, T: Copy>( + mut op: Op, + mut cond: Cond, + sleep_delta: Delta, + timeout_delta: Option<Delta>, +) -> Result<T> +where + Op: FnMut() -> Result<T>, + Cond: FnMut(&T) -> bool, +{ + let start = Instant::now(); + let sleep = !sleep_delta.is_zero(); + + if sleep { + might_sleep(Location::caller()); + } + + 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 let Some(timeout_delta) = timeout_delta { + 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 { + fsleep(sleep_delta); + } + // fsleep() could be busy-wait loop so we always call cpu_relax(). + cpu_relax(); + } +}
On 28 Jan 2025, at 7:29, FUJITA Tomonori wrote: > On Tue, 28 Jan 2025 08:49:37 +0800 > Gary Guo <gary@garyguo.net> wrote: > >> On Mon, 27 Jan 2025 15:31:47 +0900 (JST) >> FUJITA Tomonori <fujita.tomonori@gmail.com> wrote: >> >>> On Mon, 27 Jan 2025 11:46:46 +0800 >>> Gary Guo <gary@garyguo.net> wrote: >>> >>>>> +#[track_caller] >>>>> +pub fn read_poll_timeout<Op, Cond, T: Copy>( >>>>> + 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::now(); >>>>> + let sleep = !sleep_delta.is_zero(); >>>>> + let timeout = !timeout_delta.is_zero(); >>>>> + >>>>> + if sleep { >>>>> + might_sleep(Location::caller()); >>>>> + } >>>>> + >>>>> + 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 timeout && start.elapsed() > timeout_delta { >>>> >>>> Re-reading this again I wonder if this is the desired behaviour? Maybe >>>> a timeout of 0 should mean check-once instead of no timeout. The >>>> special-casing of 0 makes sense in C but in Rust we should use `None` >>>> to mean it instead? >>> >>> It's the behavior of the C version; the comment of this function says: >>> >>> * @timeout_us: Timeout in us, 0 means never timeout >>> >>> You meant that waiting for a condition without a timeout is generally >>> a bad idea? If so, can we simply return EINVAL for zero Delta? >>> >> >> No, I think we should still keep the ability to represent indefinite >> wait (no timeout) but we should use `None` to represent this rather >> than `Delta::ZERO`. >> >> I know that we use 0 to mean indefinite wait in C, I am saying that >> it's not the most intuitive way to represent in Rust. >> >> Intuitively, a timeout of 0 should be closer to a timeout of 1 and thus >> should mean "return with ETIMEDOUT immedidately" rather than "wait >> forever". >> >> In C since we don't have a very good sum type support, so we >> special case 0 to be the special value to represent indefinite wait, >> but I don't think we need to repeat this in Rust. > > Understood, thanks. How about the following code? > > +#[track_caller] > +pub fn read_poll_timeout<Op, Cond, T: Copy>( > + mut op: Op, > + mut cond: Cond, > + sleep_delta: Delta, > + timeout_delta: Option<Delta>, > +) -> Result<T> > +where > + Op: FnMut() -> Result<T>, > + Cond: FnMut(&T) -> bool, > +{ > + let start = Instant::now(); > + let sleep = !sleep_delta.is_zero(); > + > + if sleep { > + might_sleep(Location::caller()); > + } > + > + 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 let Some(timeout_delta) = timeout_delta { > + 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 { > + fsleep(sleep_delta); > + } > + // fsleep() could be busy-wait loop so we always call cpu_relax(). > + cpu_relax(); > + } > +} I wonder if it makes sense to then switch `Delta` to wrap a `NonZeroI64` and forbid deltas with 0 nanoseconds with that and use the niche optimization. Not sure if we make other apis horrible by that, but this would prevent deltas that encode no time passing. Thanks, Fiona
On 25 Jan 2025, at 11:18, FUJITA Tomonori wrote: > Add read_poll_timeout functions which poll periodically until a > condition is met or a 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. > > 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. > > The sleep_before_read argument isn't supported since there is no user > for now. It's rarely used in the C version. > > read_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. > > 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/cpu.rs | 13 +++++++ > rust/kernel/error.rs | 1 + > rust/kernel/io.rs | 5 +++ > rust/kernel/io/poll.rs | 79 ++++++++++++++++++++++++++++++++++++++++++ > rust/kernel/lib.rs | 2 ++ > 7 files changed, 114 insertions(+) > create mode 100644 rust/helpers/kernel.c > create mode 100644 rust/kernel/cpu.rs > create mode 100644 rust/kernel/io.rs > create mode 100644 rust/kernel/io/poll.rs > (..) > diff --git a/rust/kernel/io/poll.rs b/rust/kernel/io/poll.rs > new file mode 100644 > index 000000000000..7a503cf643a1 > --- /dev/null > +++ b/rust/kernel/io/poll.rs > @@ -0,0 +1,79 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +//! IO polling. > +//! > +//! C header: [`include/linux/iopoll.h`](srctree/include/linux/iopoll.h). > + > +use crate::{ > + cpu::cpu_relax, > + error::{code::*, Result}, > + time::{delay::fsleep, Delta, Instant}, > +}; > + > +use core::panic::Location; > + > +/// Polls periodically until a condition is met or a timeout is reached. > +/// > +/// Public but hidden since it should only be used from public macros. This states the function should be hidden, but I don’t see a `#[doc(hidden)]` in here so bit confused by that comment what part now is hidden. Thanks, Fiona > +/// > +/// ```rust > +/// use kernel::io::poll::read_poll_timeout; > +/// use kernel::time::Delta; > +/// use kernel::sync::{SpinLock, new_spinlock}; > +/// > +/// let lock = KBox::pin_init(new_spinlock!(()), kernel::alloc::flags::GFP_KERNEL)?; > +/// let g = lock.lock(); > +/// read_poll_timeout(|| Ok(()), |()| true, Delta::from_micros(42), Delta::from_micros(42)); > +/// drop(g); > +/// > +/// # Ok::<(), Error>(()) > +/// ``` > +#[track_caller] > +pub fn read_poll_timeout<Op, Cond, T: Copy>( > + 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::now(); > + let sleep = !sleep_delta.is_zero(); > + let timeout = !timeout_delta.is_zero(); > + > + if sleep { > + might_sleep(Location::caller()); > + } > + > + 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 timeout && 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 { > + fsleep(sleep_delta); > + } > + // fsleep() could be busy-wait loop so we always call cpu_relax(). > + cpu_relax(); > + } > +} > + > +fn might_sleep(loc: &Location<'_>) { > + // SAFETY: FFI call. > + unsafe { > + crate::bindings::__might_sleep_precision( > + loc.file().as_ptr().cast(), > + loc.file().len() as i32, > + loc.line() as i32, > + ) > + } > +} > diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs > index 545d1170ee63..c477701b2efa 100644 > --- a/rust/kernel/lib.rs > +++ b/rust/kernel/lib.rs > @@ -35,6 +35,7 @@ > pub mod block; > #[doc(hidden)] > pub mod build_assert; > +pub mod cpu; > pub mod cred; > pub mod device; > pub mod error; > @@ -42,6 +43,7 @@ > pub mod firmware; > pub mod fs; > pub mod init; > +pub mod io; > pub mod ioctl; > pub mod jump_label; > #[cfg(CONFIG_KUNIT)] > -- > 2.43.0
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index d16aeda7a558..7ab71a6d4603 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -13,6 +13,7 @@ #include "build_bug.c" #include "cred.c" #include "err.c" +#include "kernel.c" #include "fs.c" #include "jump_label.c" #include "kunit.c" diff --git a/rust/helpers/kernel.c b/rust/helpers/kernel.c new file mode 100644 index 000000000000..9dff28f4618e --- /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_precision(const char *file, int len, int line) +{ + __might_sleep_precision(file, len, line); +} diff --git a/rust/kernel/cpu.rs b/rust/kernel/cpu.rs new file mode 100644 index 000000000000..eeeff4be84fa --- /dev/null +++ b/rust/kernel/cpu.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Processor related primitives. +//! +//! C header: [`include/linux/processor.h`](srctree/include/linux/processor.h). + +/// Lower CPU power consumption or yield to a hyperthreaded twin processor. +/// +/// It also happens to serve as a compiler barrier. +pub fn cpu_relax() { + // SAFETY: FFI call. + unsafe { bindings::cpu_relax() } +} diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index f6ecf09cb65f..8858eb13b3df 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -64,6 +64,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..7a503cf643a1 --- /dev/null +++ b/rust/kernel/io/poll.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! IO polling. +//! +//! C header: [`include/linux/iopoll.h`](srctree/include/linux/iopoll.h). + +use crate::{ + cpu::cpu_relax, + error::{code::*, Result}, + time::{delay::fsleep, Delta, Instant}, +}; + +use core::panic::Location; + +/// Polls periodically until a condition is met or a timeout is reached. +/// +/// Public but hidden since it should only be used from public macros. +/// +/// ```rust +/// use kernel::io::poll::read_poll_timeout; +/// use kernel::time::Delta; +/// use kernel::sync::{SpinLock, new_spinlock}; +/// +/// let lock = KBox::pin_init(new_spinlock!(()), kernel::alloc::flags::GFP_KERNEL)?; +/// let g = lock.lock(); +/// read_poll_timeout(|| Ok(()), |()| true, Delta::from_micros(42), Delta::from_micros(42)); +/// drop(g); +/// +/// # Ok::<(), Error>(()) +/// ``` +#[track_caller] +pub fn read_poll_timeout<Op, Cond, T: Copy>( + 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::now(); + let sleep = !sleep_delta.is_zero(); + let timeout = !timeout_delta.is_zero(); + + if sleep { + might_sleep(Location::caller()); + } + + 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 timeout && 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 { + fsleep(sleep_delta); + } + // fsleep() could be busy-wait loop so we always call cpu_relax(). + cpu_relax(); + } +} + +fn might_sleep(loc: &Location<'_>) { + // SAFETY: FFI call. + unsafe { + crate::bindings::__might_sleep_precision( + loc.file().as_ptr().cast(), + loc.file().len() as i32, + loc.line() as i32, + ) + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 545d1170ee63..c477701b2efa 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -35,6 +35,7 @@ pub mod block; #[doc(hidden)] pub mod build_assert; +pub mod cpu; pub mod cred; pub mod device; pub mod error; @@ -42,6 +43,7 @@ pub mod firmware; pub mod fs; pub mod init; +pub mod io; pub mod ioctl; pub mod jump_label; #[cfg(CONFIG_KUNIT)]
Add read_poll_timeout functions which poll periodically until a condition is met or a 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. 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. The sleep_before_read argument isn't supported since there is no user for now. It's rarely used in the C version. read_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. 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/cpu.rs | 13 +++++++ rust/kernel/error.rs | 1 + rust/kernel/io.rs | 5 +++ rust/kernel/io/poll.rs | 79 ++++++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 2 ++ 7 files changed, 114 insertions(+) create mode 100644 rust/helpers/kernel.c create mode 100644 rust/kernel/cpu.rs create mode 100644 rust/kernel/io.rs create mode 100644 rust/kernel/io/poll.rs