Re: [PATCH 3/5] rust: sync: add WaitQueue infrastructure
Onur Özkan <[email protected]> Tue, 28 Jul 2026 09:11:46 +0300
| Newsgroups | org.kernel.vger.rust-for-linux,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
On Mon, 27 Jul 2026 00:36:09 +0200 Danilo Krummrich <[email protected]> wrote: > Implement a wait queue wrapping the kernel's struct wait_queue_head, > with wait_event()-style methods that take a condition closure directly. > > The API mirrors the C wait_event() family: > > - wait_event() > - wait_event_interruptible() > - wait_event_timeout() > - wait_event_interruptible_timeout() > - wake_up() / wake_up_all() / wake_up_sync() > > Interruptible and timeout variants return Result<(), WaitError>, where > WaitError maps to ERESTARTSYS (signal) or ETIMEDOUT (timeout). > > Signed-off-by: Danilo Krummrich <[email protected]> > --- > rust/kernel/sync.rs | 6 + > rust/kernel/sync/wait.rs | 388 +++++++++++++++++++++++++++++++++++++++ > 2 files changed, 394 insertions(+) > create mode 100644 rust/kernel/sync/wait.rs > > diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs > index df4f2604ff9b..31f0999e5747 100644 > --- a/rust/kernel/sync.rs > +++ b/rust/kernel/sync.rs > @@ -21,6 +21,7 @@ > pub mod rcu; > mod refcount; > mod set_once; > +mod wait; > > pub use arc::{Arc, ArcBorrow, UniqueArc}; > pub use completion::Completion; > @@ -38,6 +39,11 @@ > pub use locked_by::LockedBy; > pub use refcount::Refcount; > pub use set_once::SetOnce; > +pub use wait::{ > + new_waitqueue, > + WaitError, > + WaitQueue, // > +}; > > /// Represents a lockdep class. > /// > diff --git a/rust/kernel/sync/wait.rs b/rust/kernel/sync/wait.rs > new file mode 100644 > index 000000000000..ba4ee0f8d4d4 > --- /dev/null > +++ b/rust/kernel/sync/wait.rs > @@ -0,0 +1,388 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +//! Wait queue. > +//! > +//! C header: [`include/linux/wait.h`](srctree/include/linux/wait.h) > + > +use super::LockClassKey; > +use crate::{ > + prelude::*, > + str::CStr, > + task::{ > + self, > + TASK_INTERRUPTIBLE, > + TASK_NORMAL, > + TASK_UNINTERRUPTIBLE, // > + }, > + time::Jiffies, > + types::Opaque, > +}; > + > +use core::{ > + pin::Pin, > + ptr, // > +}; > + > +/// Creates a [`WaitQueue`] initialiser with the given name and a newly-created lock class. > +#[macro_export] > +macro_rules! new_waitqueue { > + ($($name:literal)?) => { > + $crate::sync::WaitQueue::new( > + $crate::optional_name!($($name)?), > + $crate::static_lock_class!(), > + ) > + }; > +} > +pub use new_waitqueue; > + > +/// Exposes the kernel's [`struct wait_queue_head`] as a Rust wait queue. > +/// > +/// A `WaitQueue` allows a thread to sleep until a caller-supplied condition becomes true, > +/// re-checking the condition on each wake-up. This matches the C `wait_event()` family of macros. > +/// > +/// For waiting with a lock guard (the condition variable pattern), use [`CondVar`](super::CondVar) > +/// instead. > +/// > +/// Instances of `WaitQueue` need a lock class and to be pinned. The recommended way to create such > +/// instances is with the [`pin_init!`] and [`new_waitqueue!`] macros. > +/// > +/// # Examples > +/// > +/// ``` > +/// use kernel::sync::{ > +/// atomic::{ > +/// Atomic, > +/// Relaxed, > +/// }, > +/// new_waitqueue, > +/// WaitQueue, > +/// }; > +/// > +/// #[pin_data] > +/// pub struct Example { > +/// value: Atomic<i32>, > +/// #[pin] > +/// queue: WaitQueue, > +/// } > +/// > +/// fn wait_for_value(e: &Example, v: i32) { > +/// e.queue.wait_event(|| e.value.load(Relaxed) == v); > +/// } > +/// > +/// fn set_value(e: &Example, v: i32) { > +/// e.value.store(v, Relaxed); > +/// e.queue.wake_up(); > +/// } > +/// ``` > +/// > +/// [`struct wait_queue_head`]: srctree/include/linux/wait.h > +#[pin_data] > +pub struct WaitQueue { > + #[pin] > + wait_queue_head: Opaque<bindings::wait_queue_head>, > +} > + > +// SAFETY: `WaitQueue` only uses a `struct wait_queue_head`, which is safe to use on any thread. > +unsafe impl Send for WaitQueue {} > + > +// SAFETY: `WaitQueue` only uses a `struct wait_queue_head`, which is safe to use on multiple > +// threads concurrently. > +unsafe impl Sync for WaitQueue {} > + > +impl WaitQueue { > + /// Constructs a new wait queue initialiser. > + pub fn new(name: &'static CStr, key: Pin<&'static LockClassKey>) -> impl PinInit<Self> { > + pin_init!(Self { > + // SAFETY: `slot` is valid while the closure is called and both `name` and `key` have > + // static lifetimes so they live indefinitely. > + wait_queue_head <- Opaque::ffi_init(|slot| unsafe { > + bindings::__init_waitqueue_head(slot, name.as_char_ptr(), key.as_ptr()) > + }), > + }) > + } > + > + /// Returns a raw pointer to the underlying `wait_queue_head`. > + #[expect(unused)] > + #[inline] > + pub(super) fn as_raw(&self) -> *mut bindings::wait_queue_head { > + self.wait_queue_head.get() > + } > + > + /// Sleeps until the condition returns `true`. > + /// > + /// The condition is checked before each sleep and after each wake-up. The wait is > + /// uninterruptible. > + #[inline] > + pub fn wait_event<F: Fn() -> bool>(&self, condition: F) { > + self.wait_event_timeout_internal(TASK_UNINTERRUPTIBLE, &condition, Jiffies::MAX); > + } > + > + /// Sleeps until the condition returns `true` or a signal is received. > + /// > + /// Returns `Ok(())` when the condition is met, or `Err(WaitError::Signal)` if interrupted > + /// by a signal. > + #[inline] > + pub fn wait_event_interruptible<F: Fn() -> bool>(&self, condition: F) -> Result<(), WaitError> { > + self.wait_event_timeout_internal(TASK_INTERRUPTIBLE, &condition, Jiffies::MAX); > + if !condition() && current!().signal_pending() { > + Err(WaitError::Signal) > + } else { > + Ok(()) > + } > + } > + > + /// Sleeps until the condition returns `true` or the timeout expires. > + /// > + /// Returns `Ok(())` when the condition is met, or `Err(WaitError::Timeout)` if the timeout > + /// elapsed first. > + #[inline] > + pub fn wait_event_timeout<F: Fn() -> bool>( > + &self, > + condition: F, > + jiffies: Jiffies, > + ) -> Result<(), WaitError> { > + let remaining = self.wait_event_timeout_internal(TASK_UNINTERRUPTIBLE, &condition, jiffies); > + if remaining == 0 && !condition() { > + Err(WaitError::Timeout) > + } else { > + Ok(()) > + } > + } > + > + /// Sleeps until the condition returns `true`, a signal is received, or the timeout expires. > + /// > + /// Returns `Ok(())` when the condition is met, or `Err(WaitError)` on signal or timeout. > + #[inline] > + pub fn wait_event_interruptible_timeout<F: Fn() -> bool>( > + &self, > + condition: F, > + jiffies: Jiffies, > + ) -> Result<(), WaitError> { > + let remaining = self.wait_event_timeout_internal(TASK_INTERRUPTIBLE, &condition, jiffies); > + if condition() { > + Ok(()) > + } else if current!().signal_pending() { > + Err(WaitError::Signal) > + } else if remaining == 0 { > + Err(WaitError::Timeout) > + } else { > + Ok(()) > + } > + } > + > + fn wait_event_timeout_internal( > + &self, > + wait_state: c_int, > + condition: &dyn Fn() -> bool, > + jiffies: Jiffies, > + ) -> Jiffies { Would it make sense to check condition() first and return early? > + let wait = Opaque::<bindings::wait_queue_entry>::uninit(); > + > + // SAFETY: `wait` points to valid memory. > + unsafe { bindings::init_wait(wait.get()) }; > + > + let mut remaining = jiffies; > + > + loop { > + // SAFETY: Both `wait` and `wait_queue_head` point to valid memory, and `wait` was > + // initialised by `init_wait()` above. > + let ret = unsafe { > + bindings::prepare_to_wait_event(self.wait_queue_head.get(), wait.get(), wait_state) > + }; > + > + if condition() { > + break; > + } > + > + if ret != 0 || remaining == 0 { > + break; > + } > + > + remaining = task::schedule_timeout(remaining); > + > + if condition() { > + break; > + } > + } > + > + // SAFETY: Both `wait` and `wait_queue_head` point to valid memory. > + unsafe { bindings::finish_wait(self.wait_queue_head.get(), wait.get()) }; > + > + remaining > + } > + > + /// Performs a single exclusive prepare-to-wait / finish-wait cycle, calling `schedule_fn` > + /// in between. > + #[expect(unused)] > + pub(super) fn wait_once_exclusive<F, R>(&self, wait_state: c_int, schedule_fn: F) -> R > + where > + F: FnOnce() -> R, > + { > + let wait = Opaque::<bindings::wait_queue_entry>::uninit(); > + > + // SAFETY: `wait` points to valid memory. > + unsafe { bindings::init_wait(wait.get()) }; > + > + // SAFETY: Both `wait` and `wait_queue_head` point to valid memory. > + unsafe { > + bindings::prepare_to_wait_exclusive(self.wait_queue_head.get(), wait.get(), wait_state) > + }; > + > + let ret = schedule_fn(); > + > + // SAFETY: Both `wait` and `wait_queue_head` point to valid memory. > + unsafe { bindings::finish_wait(self.wait_queue_head.get(), wait.get()) }; > + > + ret > + } > + > + /// Wakes up waiters. > + /// > + /// Wakes all non-exclusive waiters and one exclusive waiter, if any. Matches C's `wake_up()`. > + #[inline] > + pub fn wake_up(&self) { > + // SAFETY: `wait_queue_head` points to valid memory. > + unsafe { bindings::__wake_up(self.wait_queue_head.get(), TASK_NORMAL, 1, ptr::null_mut()) }; > + } > + > + /// Wakes up all waiters. > + /// > + /// Wakes all non-exclusive and all exclusive waiters, if any. > + /// Matches C's `wake_up_all()`. > + #[inline] > + pub fn wake_up_all(&self) { > + // SAFETY: `wait_queue_head` points to valid memory. > + unsafe { bindings::__wake_up(self.wait_queue_head.get(), TASK_NORMAL, 0, ptr::null_mut()) }; > + } > + > + /// Like [`wake_up()`](Self::wake_up), but hints to the scheduler that the current task is > + /// about to sleep, so the woken task should be scheduled on the same CPU to avoid unnecessary > + /// migration. Matches C's `wake_up_sync()`. > + #[inline] > + pub fn wake_up_sync(&self) { > + // SAFETY: `wait_queue_head` points to valid memory. > + unsafe { bindings::__wake_up_sync(self.wait_queue_head.get(), TASK_NORMAL) }; > + } > + > + /// Wakes up all waiters and clears poll registrations. > + /// > + /// Used when a wait queue is about to be freed, to ensure epoll items are properly removed. > + /// Matches C's `wake_up_pollfree()`. > + #[inline] > + #[expect(unused)] > + pub(super) fn wake_up_pollfree(&self) { > + // SAFETY: `wait_queue_head` points to valid memory. > + unsafe { bindings::__wake_up_pollfree(self.wait_queue_head.get()) }; > + } > +} > + > +/// Error returned by [`WaitQueue`] wait functions. > +#[derive(Debug, PartialEq)] > +pub enum WaitError { > + /// Interrupted by a signal. > + Signal, > + /// The timeout elapsed without the condition being met. > + Timeout, > +} > + > +impl From<WaitError> for Error { > + #[inline] > + fn from(e: WaitError) -> Error { > + match e { > + WaitError::Signal => ERESTARTSYS, > + WaitError::Timeout => ETIMEDOUT, > + } > + } > +} > + > +#[macros::kunit_tests(rust_waitqueue)] > +mod tests { > + use super::*; > + use crate::{ > + sync::{ > + atomic::{ > + Atomic, > + Relaxed, // > + }, > + Arc, > + }, > + time::{ > + delay::fsleep, > + Delta, // > + }, > + workqueue, > + }; > + > + #[pin_data] > + struct State { > + value: Atomic<i32>, > + #[pin] > + wq: WaitQueue, > + } > + > + impl State { > + fn new() -> Result<Arc<Self>> { > + Arc::pin_init( > + pin_init!(Self { > + value: Atomic::new(0), > + wq <- new_waitqueue!(), > + }), > + GFP_KERNEL, > + ) > + } > + } > + > + #[test] > + fn wait_event_from_work() { > + let s = State::new().unwrap(); > + > + let s2 = s.clone(); > + workqueue::system_dfl() > + .try_spawn(GFP_KERNEL, move || { > + s2.value.store(1, Relaxed); > + s2.wq.wake_up(); > + }) > + .unwrap(); > + > + s.wq.wait_event(|| s.value.load(Relaxed) == 1); > + assert_eq!(s.value.load(Relaxed), 1); > + } > + > + #[test] > + fn wait_event_condition_already_true() { > + let s = State::new().unwrap(); > + s.value.store(1, Relaxed); > + > + s.wq.wait_event(|| s.value.load(Relaxed) == 1); > + assert_eq!(s.value.load(Relaxed), 1); > + } > + > + #[test] > + fn wait_event_condition_not_yet_met() { > + let s = State::new().unwrap(); > + > + let s2 = s.clone(); > + workqueue::system_dfl() > + .try_spawn(GFP_KERNEL, move || { > + s2.value.store(1, Relaxed); > + s2.wq.wake_up_all(); > + > + fsleep(Delta::from_millis(50)); > + > + s2.value.store(2, Relaxed); > + s2.wq.wake_up_all(); > + }) > + .unwrap(); > + > + s.wq.wait_event(|| s.value.load(Relaxed) == 2); > + assert_eq!(s.value.load(Relaxed), 2); > + } > + > + #[test] > + fn wait_event_timeout_expires() { > + let s = State::new().unwrap(); > + > + let ret = s.wq.wait_event_timeout(|| s.value.load(Relaxed) == 1, 1); > + assert_eq!(ret, Err(WaitError::Timeout)); > + } > +} > -- > 2.55.0 >