Skip to content

Commit d020b7a

Browse files
committed
Add a condition variable implementation to the sync module.
1 parent 715a111 commit d020b7a

File tree

4 files changed

+126
-0
lines changed

4 files changed

+126
-0
lines changed

rust/helpers.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ void rust_helper_spin_unlock(spinlock_t *lock)
4747
}
4848
EXPORT_SYMBOL(rust_helper_spin_unlock);
4949

50+
void rust_helper_init_wait(struct wait_queue_entry *wq_entry)
51+
{
52+
init_wait(wq_entry);
53+
}
54+
EXPORT_SYMBOL(rust_helper_init_wait);
55+
5056
// See https://github.com/rust-lang/rust-bindgen/issues/1671
5157
static_assert(__builtin_types_compatible_p(size_t, uintptr_t),
5258
"size_t must match uintptr_t, what architecture is this??");

rust/kernel/bindings_helper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <linux/uaccess.h>
1010
#include <linux/version.h>
1111
#include <linux/miscdevice.h>
12+
#include <linux/poll.h>
1213

1314
// `bindgen` gets confused at certain things
1415
const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL;

rust/kernel/sync/condvar.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! A condition variable.
4+
//!
5+
//! This module allows Rust code to use the kernel's [`struct wait_queue_head`] as a condition
6+
//! variable.
7+
8+
use super::{Guard, Lock, NeedsLockClass};
9+
use crate::{bindings, CStr};
10+
use core::{cell::UnsafeCell, marker::PhantomPinned, mem::MaybeUninit, pin::Pin};
11+
12+
extern "C" {
13+
fn rust_helper_init_wait(wq: *mut bindings::wait_queue_entry);
14+
}
15+
16+
/// Safely initialises a [`Condvar`] with the given name, generating a new lock class.
17+
#[macro_export]
18+
macro_rules! condvar_init {
19+
($condvar:expr, $name:literal) => {
20+
$crate::init_with_lockdep!($condvar, $name)
21+
};
22+
}
23+
24+
// TODO: `bindgen` is not generating this constant. Figure out why.
25+
const POLLFREE: u32 = 0x4000;
26+
27+
/// Exposes the kernel's [`struct wait_queue_head`] as a condition variable. It allows to caller to
28+
/// atomically release the given lock and go to sleep. It reacquires the lock when it wakes up. And
29+
/// it wakes up when notified by another thread (via [`Condvar::notify_one`] or
30+
/// [`Condvar::notify_all`]) or because the thread received a signal.
31+
///
32+
/// [`struct wait_queue_head`]: ../../../include/linux/wait.h
33+
pub struct Condvar {
34+
pub(crate) wait_list: UnsafeCell<bindings::wait_queue_head>,
35+
36+
/// A condvar needs to be pinned because it contains a [`struct list_head`] that is
37+
/// self-referential, so it cannot be safely moved once it is initialised.
38+
_pin: PhantomPinned,
39+
}
40+
41+
impl Condvar {
42+
/// Constructs a new conditional variable.
43+
///
44+
/// # Safety
45+
///
46+
/// The caller must call `Condvar::init` before using the conditional variable.
47+
pub unsafe fn new() -> Self {
48+
Self {
49+
wait_list: UnsafeCell::new(bindings::wait_queue_head::default()),
50+
_pin: PhantomPinned,
51+
}
52+
}
53+
54+
/// Atomically releases the given lock (whose ownership is proven by the guard) and puts the
55+
/// thread to sleep. It wakes up when notified by [`Condvar::notify_one`] or
56+
/// [`Condvar::notify_all`], or when the thread receives a signal.
57+
pub fn wait<L: Lock>(&self, g: &Guard<L>) {
58+
let l = g.lock;
59+
let mut wait = MaybeUninit::<bindings::wait_queue_entry>::uninit();
60+
unsafe {
61+
// SAFETY: `wait` points to valid memory.
62+
rust_helper_init_wait(wait.as_mut_ptr());
63+
// SAFETY: Both `wait` and `wait_list` point to valid memory.
64+
bindings::prepare_to_wait_exclusive(
65+
self.wait_list.get(),
66+
wait.as_mut_ptr(),
67+
bindings::TASK_INTERRUPTIBLE as _,
68+
);
69+
// SAFETY: The guard is evidence that the caller owns the lock.
70+
l.unlock();
71+
bindings::schedule();
72+
l.lock_noguard();
73+
// SAFETY: Both `wait` and `wait_list` point to valid memory.
74+
bindings::finish_wait(self.wait_list.get(), wait.as_mut_ptr());
75+
}
76+
}
77+
78+
/// Calls the kernel function to notify the appropriate number of threads with the given flags.
79+
fn notify(&self, count: i32, flags: u32) {
80+
// SAFETY: `wait_list` points to valid memory.
81+
unsafe {
82+
bindings::__wake_up(
83+
self.wait_list.get(),
84+
bindings::TASK_NORMAL,
85+
count,
86+
flags as _,
87+
)
88+
};
89+
}
90+
91+
/// Wakes a single waiter up, if any. This is not 'sticky' in the sense that if no thread is
92+
/// waiting, the notification is lost completely (as opposed to automatically waking up the
93+
/// next waiter).
94+
pub fn notify_one(&self) {
95+
self.notify(1, 0);
96+
}
97+
98+
/// Wakes all waiters up, if any. This is not 'sticky' in the sense that if no thread is
99+
/// waiting, the notification is lost completely (as opposed to automatically waking up the
100+
/// next waiter).
101+
pub fn notify_all(&self) {
102+
self.notify(0, 0);
103+
}
104+
105+
/// Wakes all waiters up. If they were added by `epoll`, they are also removed from the list of
106+
/// waiters. This is useful when cleaning up a condition variable that may be waited on by
107+
/// threads that use `epoll`.
108+
pub fn free_waiters(&self) {
109+
self.notify(1, bindings::POLLHUP | POLLFREE);
110+
}
111+
}
112+
113+
impl NeedsLockClass for Condvar {
114+
unsafe fn init(self: Pin<&Self>, name: CStr<'static>, key: *mut bindings::lock_class_key) {
115+
bindings::__init_waitqueue_head(self.wait_list.get(), name.as_ptr() as _, key);
116+
}
117+
}

rust/kernel/sync/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
use crate::{bindings, CStr};
2121
use core::pin::Pin;
2222

23+
mod condvar;
2324
mod guard;
2425
mod mutex;
2526
mod spinlock;
2627

28+
pub use condvar::Condvar;
2729
pub use guard::{Guard, Lock};
2830
pub use mutex::Mutex;
2931
pub use spinlock::SpinLock;

0 commit comments

Comments
 (0)