From ba93231f3de5395e83a8caeabb0bfd5a4a5862eb Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Thu, 11 Mar 2021 16:55:44 +0000 Subject: [PATCH] Add a spinlock implementation to the `sync` module. --- drivers/char/rust_example.rs | 18 +++++- include/linux/spinlock.h | 17 ++++-- rust/helpers.c | 23 ++++++++ rust/kernel/sync/mod.rs | 5 +- rust/kernel/sync/spinlock.rs | 108 +++++++++++++++++++++++++++++++++++ 5 files changed, 162 insertions(+), 9 deletions(-) create mode 100644 rust/kernel/sync/spinlock.rs diff --git a/drivers/char/rust_example.rs b/drivers/char/rust_example.rs index d24480798b2a23..e3582f7373ab49 100644 --- a/drivers/char/rust_example.rs +++ b/drivers/char/rust_example.rs @@ -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, @@ -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()); } diff --git a/include/linux/spinlock.h b/include/linux/spinlock.h index 79897841a2cc8a..a022992725be34 100644 --- a/include/linux/spinlock.h +++ b/include/linux/spinlock.h @@ -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 diff --git a/rust/helpers.c b/rust/helpers.c index 5da1293675ca44..fcf1dbbd6cdda4 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -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??"); diff --git a/rust/kernel/sync/mod.rs b/rust/kernel/sync/mod.rs index c1bf72bcbb0e15..7fb61af0270778 100644 --- a/rust/kernel/sync/mod.rs +++ b/rust/kernel/sync/mod.rs @@ -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 { diff --git a/rust/kernel/sync/spinlock.rs b/rust/kernel/sync/spinlock.rs new file mode 100644 index 00000000000000..3f3aacbcb8f081 --- /dev/null +++ b/rust/kernel/sync/spinlock.rs @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! A kernel spinlock. +//! +//! This module allows Rust code to use the kernel's [`struct spinlock`]. +//! +//! See . + +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 (spinning) 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 { + spin_lock: UnsafeCell, + + /// 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, +} + +// SAFETY: `SpinLock` can be transferred across thread boundaries iff the data it protects can. +unsafe impl Send for SpinLock {} + +// SAFETY: `SpinLock` serialises the interior mutability it provides, so it is `Sync` as long as the +// data it protects is `Send`. +unsafe impl Sync for SpinLock {} + +impl SpinLock { + /// 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 SpinLock { + /// 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.lock_noguard(); + // SAFETY: The spinlock was just acquired. + unsafe { Guard::new(self) } + } +} + +impl NeedsLockClass for SpinLock { + 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 Lock for SpinLock { + 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 { + &self.data + } +}