Skip to main content

kernel_api/sync/
mutex.rs

1use core::convert::Into;
2use core::mem::ManuallyDrop;
3use core::panic::Location;
4use core::sync::atomic::{AtomicPtr, AtomicU8, AtomicUsize, Ordering};
5use log::warn;
6
7/// A mutual exclusion primitive useful for protecting shared data
8pub type Spinlock<T: ?Sized> = lock_api::Mutex<RawSpinlock, T>;
9
10/// An RAII implementation of a “scoped lock” of a mutex.
11/// 
12/// When this structure is dropped (falls out of scope), the lock will be unlocked.
13pub type SpinlockGuard<'a, T: ?Sized> = lock_api::MutexGuard<'a, RawSpinlock, T>;
14
15pub type MappedSpinlockGuard<'a, T: ?Sized> = lock_api::MappedMutexGuard<'a, RawSpinlock, T>;
16
17/// Extension functions to [`SpinlockGuard`]
18pub trait SpinlockGuardExt {
19    /// Unlock the spinlock without enabling interrupts, regardless of whether interrupts were enabled
20    /// before the spinlock was locked
21    fn unlock_no_interrupts(this: Self);
22}
23
24impl<T> SpinlockGuardExt for SpinlockGuard<'_, T> {
25    fn unlock_no_interrupts(this: Self) {
26        let this = ManuallyDrop::new(this);
27        unsafe {
28            let spinlock = Self::mutex(&this).raw();
29            spinlock.unlock_no_interrupts();
30        }
31    }
32}
33
34#[derive(Debug, Copy, Clone, Eq, PartialEq)]
35enum State {
36    Unlocked,
37    Locked,
38}
39
40impl State {
41    const fn const_into_u8(self) -> u8 {
42        match self {
43            State::Unlocked => 0,
44            State::Locked => 1,
45        }
46    }
47
48    const fn const_from_u8(value: u8) -> Result<Self, ()> {
49        match value {
50            0 => Ok(State::Unlocked),
51            1 => Ok(State::Locked),
52            _ => Err(())
53        }
54    }
55}
56
57impl From<State> for u8 {
58    fn from(value: State) -> Self {
59        value.const_into_u8()
60    }
61}
62
63impl TryFrom<u8> for State {
64    type Error = ();
65
66    fn try_from(value: u8) -> Result<Self, Self::Error> {
67        Self::const_from_u8(value)
68    }
69}
70
71pub struct RawSpinlock {
72    state: AtomicU8,
73    irq_state: AtomicUsize,
74    location: AtomicPtr<Location<'static>>,
75}
76
77unsafe impl Send for RawSpinlock {}
78
79unsafe impl Sync for RawSpinlock {}
80
81impl RawSpinlock {
82    unsafe fn unlock_no_interrupts(&self) {
83        let old_state = self.state.swap(State::Unlocked.into(), Ordering::Release);
84        let old_state = State::try_from(old_state).expect("Spinlock in undefined state");
85
86        match old_state {
87            State::Unlocked => unreachable!("Mutex was unlocked while unlocked"),
88            State::Locked => {},
89        }
90    }
91    
92    fn lock_location(&self) -> Option<&'static Location<'static>> {
93        let location = self.location.load(Ordering::Relaxed);
94        unsafe { location.as_ref::<'static>() }
95    }
96}
97
98unsafe impl lock_api::RawMutex for RawSpinlock {
99    const INIT: Self = Self {
100        state: AtomicU8::new(State::Unlocked.const_into_u8()),
101        irq_state: AtomicUsize::new(0),
102        location: AtomicPtr::new(core::ptr::null_mut()),
103    };
104
105    type GuardMarker = lock_api::GuardNoSend; // Dropping guard on other core would cause interrupts to be enabled in the wrong place
106
107    #[track_caller]
108    fn lock(&self) {
109        let irq_state = crate::bridge::irq::disable();
110
111        let mut p = true;
112        while let Err(_) = self.state.compare_exchange_weak(
113            State::Unlocked.into(),
114            State::Locked.into(),
115            Ordering::Acquire,
116            Ordering::Relaxed
117        ) {
118            core::hint::spin_loop();
119            if p {
120                p = false;
121                warn!("locked at {:?}", self.lock_location());
122            }
123        }
124
125        self.irq_state.store(irq_state, Ordering::Relaxed);
126        self.location.store(Location::caller() as *const _ as *mut _, Ordering::Relaxed);
127    }
128
129    fn try_lock(&self) -> bool {
130        let irq_state = crate::bridge::irq::disable();
131        let success = self.state.compare_exchange(
132            State::Unlocked.into(),
133            State::Locked.into(),
134            Ordering::Acquire,
135            Ordering::Relaxed
136        ).is_ok();
137
138        if !success { crate::bridge::irq::set(irq_state) }
139        else { self.irq_state.store(irq_state, Ordering::Relaxed) }
140
141        success
142    }
143
144    unsafe fn unlock(&self) {
145        let old_irq_state = self.irq_state.load(Ordering::Relaxed);
146        let old_state = self.state.swap(State::Unlocked.into(), Ordering::Release);
147        let old_state = State::try_from(old_state).expect("Spinlock in undefined state");
148
149        match old_state {
150            State::Unlocked => unreachable!("Mutex was unlocked while unlocked"),
151            State::Locked => crate::bridge::irq::set(old_irq_state),
152        }
153    }
154}