Skip to content

Commit 58ab534

Browse files
authored
Merge pull request #104 from Rust-for-Linux/condvar
Add a condition variable implementation to the `sync` module.
2 parents a116223 + 0098adc commit 58ab534

File tree

5 files changed

+184
-3
lines changed

5 files changed

+184
-3
lines changed

drivers/char/rust_example.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ use alloc::boxed::Box;
1010
use core::pin::Pin;
1111
use kernel::prelude::*;
1212
use kernel::{
13-
chrdev, cstr,
13+
chrdev, condvar_init, cstr,
1414
file_operations::FileOperations,
1515
miscdev, mutex_init, spinlock_init,
16-
sync::{Mutex, SpinLock},
16+
sync::{CondVar, Mutex, SpinLock},
1717
};
1818

1919
module! {
@@ -86,6 +86,20 @@ impl KernelModule for RustExample {
8686
mutex_init!(data.as_ref(), "RustExample::init::data1");
8787
*data.lock() = 10;
8888
println!("Value: {}", *data.lock());
89+
90+
// SAFETY: `init` is called below.
91+
let cv = Pin::from(Box::try_new(unsafe { CondVar::new() })?);
92+
condvar_init!(cv.as_ref(), "RustExample::init::cv1");
93+
{
94+
let guard = data.lock();
95+
#[allow(clippy::while_immutable_condition)]
96+
while *guard != 10 {
97+
cv.wait(&guard);
98+
}
99+
}
100+
cv.notify_one();
101+
cv.notify_all();
102+
cv.free_waiters();
89103
}
90104

91105
// Test spinlocks.
@@ -95,13 +109,27 @@ impl KernelModule for RustExample {
95109
spinlock_init!(data.as_ref(), "RustExample::init::data2");
96110
*data.lock() = 10;
97111
println!("Value: {}", *data.lock());
112+
113+
// SAFETY: `init` is called below.
114+
let cv = Pin::from(Box::try_new(unsafe { CondVar::new() })?);
115+
condvar_init!(cv.as_ref(), "RustExample::init::cv2");
116+
{
117+
let guard = data.lock();
118+
#[allow(clippy::while_immutable_condition)]
119+
while *guard != 10 {
120+
cv.wait(&guard);
121+
}
122+
}
123+
cv.notify_one();
124+
cv.notify_all();
125+
cv.free_waiters();
98126
}
99127

100128
// Including this large variable on the stack will trigger
101129
// stack probing on the supported archs.
102130
// This will verify that stack probing does not lead to
103131
// any errors if we need to link `__rust_probestack`.
104-
let x: [u64; 1028] = core::hint::black_box([5; 1028]);
132+
let x: [u64; 514] = core::hint::black_box([5; 514]);
105133
println!("Large array has length: {}", x.len());
106134

107135
let mut chrdev_reg =

rust/helpers.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <linux/bug.h>
44
#include <linux/build_bug.h>
55
#include <linux/uaccess.h>
6+
#include <linux/sched/signal.h>
67

78
void rust_helper_BUG(void)
89
{
@@ -47,6 +48,18 @@ void rust_helper_spin_unlock(spinlock_t *lock)
4748
}
4849
EXPORT_SYMBOL(rust_helper_spin_unlock);
4950

51+
void rust_helper_init_wait(struct wait_queue_entry *wq_entry)
52+
{
53+
init_wait(wq_entry);
54+
}
55+
EXPORT_SYMBOL(rust_helper_init_wait);
56+
57+
int rust_helper_signal_pending(void)
58+
{
59+
return signal_pending(current);
60+
}
61+
EXPORT_SYMBOL(rust_helper_signal_pending);
62+
5063
// See https://github.com/rust-lang/rust-bindgen/issues/1671
5164
static_assert(__builtin_types_compatible_p(size_t, uintptr_t),
5265
"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: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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, c_types, 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+
fn rust_helper_signal_pending() -> c_types::c_int;
15+
}
16+
17+
/// Safely initialises a [`CondVar`] with the given name, generating a new lock class.
18+
#[macro_export]
19+
macro_rules! condvar_init {
20+
($condvar:expr, $name:literal) => {
21+
$crate::init_with_lockdep!($condvar, $name)
22+
};
23+
}
24+
25+
// TODO: `bindgen` is not generating this constant. Figure out why.
26+
const POLLFREE: u32 = 0x4000;
27+
28+
/// Exposes the kernel's [`struct wait_queue_head`] as a condition variable. It allows the caller to
29+
/// atomically release the given lock and go to sleep. It reacquires the lock when it wakes up. And
30+
/// it wakes up when notified by another thread (via [`CondVar::notify_one`] or
31+
/// [`CondVar::notify_all`]) or because the thread received a signal.
32+
///
33+
/// [`struct wait_queue_head`]: ../../../include/linux/wait.h
34+
pub struct CondVar {
35+
pub(crate) wait_list: UnsafeCell<bindings::wait_queue_head>,
36+
37+
/// A condvar needs to be pinned because it contains a [`struct list_head`] that is
38+
/// self-referential, so it cannot be safely moved once it is initialised.
39+
_pin: PhantomPinned,
40+
}
41+
42+
// SAFETY: `CondVar` only uses a `struct wait_queue_head`, which is safe to use on any thread.
43+
unsafe impl Send for CondVar {}
44+
45+
// SAFETY: `CondVar` only uses a `struct wait_queue_head`, which is safe to use on multiple threads
46+
// concurrently.
47+
unsafe impl Sync for CondVar {}
48+
49+
impl CondVar {
50+
/// Constructs a new conditional variable.
51+
///
52+
/// # Safety
53+
///
54+
/// The caller must call `CondVar::init` before using the conditional variable.
55+
pub unsafe fn new() -> Self {
56+
Self {
57+
wait_list: UnsafeCell::new(bindings::wait_queue_head::default()),
58+
_pin: PhantomPinned,
59+
}
60+
}
61+
62+
/// Atomically releases the given lock (whose ownership is proven by the guard) and puts the
63+
/// thread to sleep. It wakes up when notified by [`CondVar::notify_one`] or
64+
/// [`CondVar::notify_all`], or when the thread receives a signal.
65+
///
66+
/// Returns whether there is a signal pending.
67+
pub fn wait<L: Lock>(&self, g: &Guard<L>) -> bool {
68+
let l = g.lock;
69+
let mut wait = MaybeUninit::<bindings::wait_queue_entry>::uninit();
70+
71+
// SAFETY: `wait` points to valid memory.
72+
unsafe { rust_helper_init_wait(wait.as_mut_ptr()) };
73+
74+
// SAFETY: Both `wait` and `wait_list` point to valid memory.
75+
unsafe {
76+
bindings::prepare_to_wait_exclusive(
77+
self.wait_list.get(),
78+
wait.as_mut_ptr(),
79+
bindings::TASK_INTERRUPTIBLE as _,
80+
);
81+
}
82+
83+
// SAFETY: The guard is evidence that the caller owns the lock.
84+
unsafe { l.unlock() };
85+
86+
// SAFETY: No arguments, switches to another thread.
87+
unsafe { bindings::schedule() };
88+
89+
l.lock_noguard();
90+
91+
// SAFETY: Both `wait` and `wait_list` point to valid memory.
92+
unsafe { bindings::finish_wait(self.wait_list.get(), wait.as_mut_ptr()) };
93+
94+
// SAFETY: No arguments, just checks `current` for pending signals.
95+
unsafe { rust_helper_signal_pending() != 0 }
96+
}
97+
98+
/// Calls the kernel function to notify the appropriate number of threads with the given flags.
99+
fn notify(&self, count: i32, flags: u32) {
100+
// SAFETY: `wait_list` points to valid memory.
101+
unsafe {
102+
bindings::__wake_up(
103+
self.wait_list.get(),
104+
bindings::TASK_NORMAL,
105+
count,
106+
flags as _,
107+
)
108+
};
109+
}
110+
111+
/// Wakes a single waiter up, if any. This is not 'sticky' in the sense that if no thread is
112+
/// waiting, the notification is lost completely (as opposed to automatically waking up the
113+
/// next waiter).
114+
pub fn notify_one(&self) {
115+
self.notify(1, 0);
116+
}
117+
118+
/// Wakes all waiters up, if any. This is not 'sticky' in the sense that if no thread is
119+
/// waiting, the notification is lost completely (as opposed to automatically waking up the
120+
/// next waiter).
121+
pub fn notify_all(&self) {
122+
self.notify(0, 0);
123+
}
124+
125+
/// Wakes all waiters up. If they were added by `epoll`, they are also removed from the list of
126+
/// waiters. This is useful when cleaning up a condition variable that may be waited on by
127+
/// threads that use `epoll`.
128+
pub fn free_waiters(&self) {
129+
self.notify(1, bindings::POLLHUP | POLLFREE);
130+
}
131+
}
132+
133+
impl NeedsLockClass for CondVar {
134+
unsafe fn init(self: Pin<&Self>, name: CStr<'static>, key: *mut bindings::lock_class_key) {
135+
bindings::__init_waitqueue_head(self.wait_list.get(), name.as_ptr() as _, key);
136+
}
137+
}

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)