Skip to content

Commit

Permalink
Add a spinlock implementation to the sync module.
Browse files Browse the repository at this point in the history
  • Loading branch information
wedsonaf committed Mar 12, 2021
1 parent 8a913df commit 715a111
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 9 deletions.
18 changes: 16 additions & 2 deletions drivers/char/rust_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
use alloc::boxed::Box;
use core::pin::Pin;
use kernel::prelude::*;
use kernel::{chrdev, cstr, file_operations::FileOperations, miscdev, mutex_init, sync::Mutex};
use kernel::{
chrdev, cstr,
file_operations::FileOperations,
miscdev, mutex_init, spinlock_init,
sync::{Mutex, SpinLock},
};

module! {
type: RustExample,
Expand Down Expand Up @@ -78,7 +83,16 @@ impl KernelModule for RustExample {
{
// SAFETY: `init` is called below.
let data = Pin::from(Box::try_new(unsafe { Mutex::new(0) })?);
mutex_init!(data.as_ref(), "RustExample::init::data");
mutex_init!(data.as_ref(), "RustExample::init::data1");
*data.lock() = 10;
println!("Value: {}", *data.lock());
}

// Test spinlocks.
{
// SAFETY: `init` is called below.
let data = Pin::from(Box::try_new(unsafe { SpinLock::new(0) })?);
spinlock_init!(data.as_ref(), "RustExample::init::data2");
*data.lock() = 10;
println!("Value: {}", *data.lock());
}
Expand Down
17 changes: 11 additions & 6 deletions include/linux/spinlock.h
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,17 @@ static __always_inline raw_spinlock_t *spinlock_check(spinlock_t *lock)

#ifdef CONFIG_DEBUG_SPINLOCK

# define spin_lock_init(lock) \
do { \
static struct lock_class_key __key; \
\
__raw_spin_lock_init(spinlock_check(lock), \
#lock, &__key, LD_WAIT_CONFIG); \
static inline void __spin_lock_init(spinlock_t *lock, const char *name,
struct lock_class_key *key)
{
__raw_spin_lock_init(spinlock_check(lock), name, key, LD_WAIT_CONFIG);
}

# define spin_lock_init(lock) \
do { \
static struct lock_class_key __key; \
\
__spin_lock_init(lock, #lock, &__key); \
} while (0)

#else
Expand Down
23 changes: 23 additions & 0 deletions rust/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@ unsigned long rust_helper_copy_to_user(void __user *to, const void *from, unsign
return copy_to_user(to, from, n);
}

void rust_helper_spin_lock_init(spinlock_t *lock, const char *name,
struct lock_class_key *key)
{
#ifdef CONFIG_DEBUG_SPINLOCK
__spin_lock_init(lock, name, key);
#else
spin_lock_init(lock);
#endif
}
EXPORT_SYMBOL(rust_helper_spin_lock_init);

void rust_helper_spin_lock(spinlock_t *lock)
{
spin_lock(lock);
}
EXPORT_SYMBOL(rust_helper_spin_lock);

void rust_helper_spin_unlock(spinlock_t *lock)
{
spin_unlock(lock);
}
EXPORT_SYMBOL(rust_helper_spin_unlock);

// See https://github.com/rust-lang/rust-bindgen/issues/1671
static_assert(__builtin_types_compatible_p(size_t, uintptr_t),
"size_t must match uintptr_t, what architecture is this??");
5 changes: 4 additions & 1 deletion rust/kernel/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ use core::pin::Pin;

mod guard;
mod mutex;
mod spinlock;

pub use guard::{Guard, Lock};
pub use mutex::Mutex;
pub use spinlock::SpinLock;

/// Safely initialises an object that has an `init` function that takes a name and a lock class as
/// arguments, for example, [`Mutex`].
/// arguments, examples of these are [`Mutex`] and [`SpinLock`]. Each of them also provides a more
/// specialised name that uses this macro.
#[doc(hidden)]
#[macro_export]
macro_rules! init_with_lockdep {
Expand Down
106 changes: 106 additions & 0 deletions rust/kernel/sync/spinlock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: GPL-2.0

//! A kernel spinlock.
//!
//! This module allows Rust code to use the kernel's [`struct spinlock`].
use super::{Guard, Lock, NeedsLockClass};
use crate::{bindings, c_types, CStr};
use core::{cell::UnsafeCell, marker::PhantomPinned, pin::Pin};

extern "C" {
#[allow(improper_ctypes)]
fn rust_helper_spin_lock_init(
lock: *mut bindings::spinlock_t,
name: *const c_types::c_char,
key: *mut bindings::lock_class_key,
);
fn rust_helper_spin_lock(lock: *mut bindings::spinlock);
fn rust_helper_spin_unlock(lock: *mut bindings::spinlock);
}

/// Safely initialises a [`SpinLock`] with the given name, generating a new lock class.
#[macro_export]
macro_rules! spinlock_init {
($spinlock:expr, $name:literal) => {
$crate::init_with_lockdep!($spinlock, $name)
};
}

/// Exposes the kernel's [`spinlock_t`]. When multiple CPUs attempt to lock the same spinlock, only
/// one at a time is allowed to progress, the others will block (spinnig) until the spinlock is
/// unlocked, at which point another CPU will be allowed to make progress.
///
/// A [`SpinLock`] must first be initialised with a call to [`SpinLock::init`] before it can be
/// used. The [`spinlock_init`] macro is provided to automatically assign a new lock class to a
/// spinlock instance.
///
/// [`SpinLock`] does not manage the interrupt state, so it can be used in only two cases: (a) when
/// the caller knows that interrupts are disabled, or (b) when callers never use it in interrupt
/// handlers (in which case it is ok for interrupts to be enabled).
///
/// [`spinlock_t`]: ../../../include/linux/spinlock.h
pub struct SpinLock<T: ?Sized> {
spin_lock: UnsafeCell<bindings::spinlock>,

/// Spinlocks are architecture-defined. So we conservatively require them to be pinned in case
/// some architecture uses self=references now or in the future.
_pin: PhantomPinned,

data: UnsafeCell<T>,
}

// SAFETY: `SpinLock` can be transferred across thread boundaries iff the data it protects can.
unsafe impl<T: ?Sized + Send> Send for SpinLock<T> {}

// SAFETY: `SpinLock` serialises the interior mutability it provides, so it is `Sync` as long as the
// data it protects is `Send`.
unsafe impl<T: ?Sized + Send> Sync for SpinLock<T> {}

impl<T> SpinLock<T> {
/// Constructs a new spinlock.
///
/// # Safety
///
/// The caller must call [`SpinLock::init`] before using the spinlock.
pub unsafe fn new(t: T) -> Self {
Self {
spin_lock: UnsafeCell::new(bindings::spinlock::default()),
data: UnsafeCell::new(t),
_pin: PhantomPinned,
}
}
}

impl<T: ?Sized> SpinLock<T> {
/// Locks the spinlock and gives the caller access to the data protected by it. Only one thread
/// at a time is allowed to access the protected data.
pub fn lock(&self) -> Guard<Self> {
self.lock_noguard();
// SAFETY: The spinlock was just acquired.
unsafe { Guard::new(self) }
}
}

impl<T: ?Sized> NeedsLockClass for SpinLock<T> {
unsafe fn init(self: Pin<&Self>, name: CStr<'static>, key: *mut bindings::lock_class_key) {
rust_helper_spin_lock_init(self.spin_lock.get(), name.as_ptr() as _, key);
}
}

impl<T: ?Sized> Lock for SpinLock<T> {
type Inner = T;

fn lock_noguard(&self) {
// SAFETY: `spin_lock` points to valid memory.
unsafe { rust_helper_spin_lock(self.spin_lock.get()) };
}

unsafe fn unlock(&self) {
rust_helper_spin_unlock(self.spin_lock.get());
}

unsafe fn locked_data(&self) -> &UnsafeCell<T> {
&self.data
}
}

0 comments on commit 715a111

Please sign in to comment.