// SPDX-License-Identifier: MIT OR Apache-2.0 // // Copyright (c) 2020-2022 Andre Richter //! Synchronization primitives. //! //! # Resources //! //! - //! - //! - use core::cell::UnsafeCell; //-------------------------------------------------------------------------------------------------- // Public Definitions //-------------------------------------------------------------------------------------------------- /// Synchronization interfaces. pub mod interface { /// Any object implementing this trait guarantees exclusive access to the data wrapped within /// the Mutex for the duration of the provided closure. pub trait Mutex { /// The type of the data that is wrapped by this mutex. type Data; /// Locks the mutex and grants the closure temporary mutable access to the wrapped data. fn lock<'a, R>(&'a self, f: impl FnOnce(&'a mut Self::Data) -> R) -> R; } /// A reader-writer exclusion type. /// /// The implementing object allows either a number of readers or at most one writer at any point /// in time. pub trait ReadWriteEx { /// The type of encapsulated data. type Data; /// Grants temporary mutable access to the encapsulated data. fn write<'a, R>(&'a self, f: impl FnOnce(&'a mut Self::Data) -> R) -> R; /// Grants temporary immutable access to the encapsulated data. fn read<'a, R>(&'a self, f: impl FnOnce(&'a Self::Data) -> R) -> R; } } /// A pseudo-lock for teaching purposes. /// /// In contrast to a real Mutex implementation, does not protect against concurrent access from /// other cores to the contained data. This part is preserved for later lessons. /// /// The lock will only be used as long as it is safe to do so, i.e. as long as the kernel is /// executing on a single core. pub struct IRQSafeNullLock where T: ?Sized, { data: UnsafeCell, } /// A pseudo-lock that is RW during the single-core kernel init phase and RO afterwards. /// /// Intended to encapsulate data that is populated during kernel init when no concurrency exists. pub struct InitStateLock where T: ?Sized, { data: UnsafeCell, } //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- unsafe impl Send for IRQSafeNullLock where T: ?Sized + Send {} unsafe impl Sync for IRQSafeNullLock where T: ?Sized + Send {} impl IRQSafeNullLock { /// Create an instance. pub const fn new(data: T) -> Self { Self { data: UnsafeCell::new(data), } } } unsafe impl Send for InitStateLock where T: ?Sized + Send {} unsafe impl Sync for InitStateLock where T: ?Sized + Send {} impl InitStateLock { /// Create an instance. pub const fn new(data: T) -> Self { Self { data: UnsafeCell::new(data), } } } //------------------------------------------------------------------------------ // OS Interface Code //------------------------------------------------------------------------------ use crate::{exception, state}; impl interface::Mutex for IRQSafeNullLock { type Data = T; fn lock<'a, R>(&'a self, f: impl FnOnce(&'a mut Self::Data) -> R) -> R { // In a real lock, there would be code encapsulating this line that ensures that this // mutable reference will ever only be given out once at a time. let data = unsafe { &mut *self.data.get() }; // Execute the closure while IRQs are masked. exception::asynchronous::exec_with_irq_masked(|| f(data)) } } impl interface::ReadWriteEx for InitStateLock { type Data = T; fn write<'a, R>(&'a self, f: impl FnOnce(&'a mut Self::Data) -> R) -> R { assert!( state::state_manager().is_init(), "InitStateLock::write called after kernel init phase" ); assert!( !exception::asynchronous::is_local_irq_masked(), "InitStateLock::write called with IRQs unmasked" ); let data = unsafe { &mut *self.data.get() }; f(data) } fn read<'a, R>(&'a self, f: impl FnOnce(&'a Self::Data) -> R) -> R { let data = unsafe { &*self.data.get() }; f(data) } } //-------------------------------------------------------------------------------------------------- // Testing //-------------------------------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; use test_macros::kernel_test; /// InitStateLock must be transparent. #[kernel_test] fn init_state_lock_is_transparent() { use core::mem::size_of; assert_eq!(size_of::>(), size_of::()); } }