1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
use core::cell::UnsafeCell;
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Deref, DerefMut};
use core::sync::atomic::{AtomicBool, AtomicU8, Ordering};

// TODO: What about sharing data between 2 interrupts on the same priority level?

/// A lock that allows sharing data between two interrupts at different priorities.
///
/// This is a general spinlock-like implementation that works even on architectures without
/// compare-and-swap instructions. This is accomplished by making use of [*Peterson's Algorithm*].
///
/// # Drawbacks
///
/// Being a general architecture-independent implementation means that it also comes with some
/// drawbacks due to not knowing anything about the target platform:
///
/// * It is limited to 2 parties sharing data. [*Peterson's Algorithm*] requires storage
///   proportional to the number of parties competing for exclusive access. With const generics it
///   might be possible to make this a compile-time parameter instead.
/// * Locking from an interrupt can fail irrecoverably. This is a fundamental limitation of trying
///   to ensure exclusive access via blocking mutexes in the presence of interrupts, and would also
///   occur when using any other generic solution (like a "real" spinlock). User code must handle a
///   failure to acquire a resource in an interrupt handler gracefully.
///
/// # Alternatives
///
/// If the drawbacks listed above are unacceptable (which is not unlikely), consider using one of
/// these alternatives for sharing data between interrupts:
///
/// * Lock-free datastructures such as those provided by [heapless].
/// * Atomics and read-modify-write operations from `core::sync::atomic` (if your target supports
///   them).
/// * A Mutex implementation that turns off interrupts (when targeting a single-core MCU).
/// * A hardware-provided Mutex peripheral (when targeting a multi-core MCU).
/// * The [Real-Time For the Masses][RTFM] framework.
///
/// [*Peterson's Algorithm*]: https://en.wikipedia.org/wiki/Peterson%27s_algorithm
/// [heapless]: https://docs.rs/heapless
/// [RTFM]: https://github.com/rtfm-rs/
#[derive(Debug)]
pub struct PriorityLock<T> {
    // TODO: Optimize memory usage when we have atomic CAS
    wants_to_enter: [AtomicBool; 2],
    turn: AtomicU8,
    data: UnsafeCell<T>,
}

impl<T> PriorityLock<T> {
    /// Creates a new lock protecting `data`.
    ///
    /// If `data` consists of zeroes, the resulting `PriorityLock` will also be zero-initialized
    /// and can be placed in `.bss` by the compiler.
    pub const fn new(data: T) -> Self {
        Self {
            wants_to_enter: [AtomicBool::new(false), AtomicBool::new(false)],
            turn: AtomicU8::new(0),
            data: UnsafeCell::new(data),
        }
    }

    /// Splits this lock into its low- and high-priority halfs.
    ///
    /// The low-priority half provides a [`lock`] method for acquiring the lock, and is meant to be
    /// used from a lower-priority context than the high-priority half (eg. a low-priority
    /// interrupt or the application's idle loop). The high-priority half provides a [`try_lock`]
    /// method for acquiring the lock, which may fail when preempting code holding the low-priority
    /// half of the lock.
    ///
    /// [`lock`]: struct.LockHalf.html#method.lock
    /// [`try_lock`]: struct.LockHalf.html#method.try_lock
    pub fn split<'a>(&'a mut self) -> (LockHalf<'a, T, PLow>, LockHalf<'a, T, PHigh>) {
        let low = LockHalf {
            lock: self,
            _p: PhantomData,
        };
        let high = LockHalf {
            lock: self,
            _p: PhantomData,
        };
        (low, high)
    }

    fn try_acquire_raw(&self, index: u8) -> Result<(), ()> {
        // Algorithm according to https://en.wikipedia.org/wiki/Peterson%27s_algorithm

        // TODO: check what happens when recursively self-locking

        let other_index = (index + 1) % 2;

        // We want to enter.
        self.wants_to_enter[usize::from(index)].store(true, Ordering::Release);
        // Give the other lock owner a chance to run.
        self.turn.store(other_index, Ordering::Release);

        // Does the other owner want to enter, and did they not give us a chance to run (by setting
        // turn to our number)?
        if self.wants_to_enter[usize::from(other_index)].load(Ordering::Acquire)
            && self.turn.load(Ordering::Acquire) == other_index
        {
            // We did not acquire the lock. Restore our flag since we no longer want to enter.
            self.wants_to_enter[usize::from(index)].store(false, Ordering::Release);

            Err(())
        } else {
            Ok(())
        }
    }

    fn block_acquire_raw(&self, index: u8) {
        let other_index = (index + 1) % 2;

        // We want to enter.
        self.wants_to_enter[usize::from(index)].store(true, Ordering::Release);
        // Give the other lock owner a chance to run.
        self.turn.store(other_index, Ordering::Release);

        // Does the other owner want to enter, and did they not give us a chance to run (by setting
        // turn to our number)?
        while self.wants_to_enter[usize::from(other_index)].load(Ordering::Acquire)
            && self.turn.load(Ordering::Acquire) == other_index
        {}
    }

    /// Safety: Unlocking an index not owned by the caller is unsound.
    unsafe fn unlock(&self, index: u8) {
        self.wants_to_enter[usize::from(index)].store(false, Ordering::Release);
    }
}

mod sealed {
    pub trait Sealed {}
}

/// Trait implemented by the lock priority types [`PHigh`] and [`PLow`].
///
/// This trait is an internal API and should not be used by user code. It cannot be implemented by
/// user-defined types.
///
/// [`PHigh`]: enum.PHigh.html
/// [`PLow`]: enum.PLow.html
pub trait LockPriority: sealed::Sealed {
    #[doc(hidden)]
    const INDEX: u8;
}

/// Type marker indicating the high-priority half of a [`PriorityLock`].
///
/// [`PriorityLock`]: struct.PriorityLock.html
#[derive(Debug)]
pub enum PHigh {}

/// Type marker indicating the low-priority half of a [`PriorityLock`].
///
/// [`PriorityLock`]: struct.PriorityLock.html
#[derive(Debug)]
pub enum PLow {}

impl sealed::Sealed for PHigh {}
impl sealed::Sealed for PLow {}

impl LockPriority for PLow {
    const INDEX: u8 = 0;
}
impl LockPriority for PHigh {
    const INDEX: u8 = 1;
}

/// Error indicating that a lock could not be acquired in a high-priority context.
///
/// With a normal lock used from an interrupt handler, this would be a deadlock.
///
/// **Note**: User code *must* handle this error in an application-specific manner! Just calling
/// `.unwrap()` is just as brittle as deadlocking.
#[allow(missing_debug_implementations)]
pub struct Deadlock {}
// (intentionally doesn't implement `Debug` so that `.unwrap()` cannot be called directly)

/// One half of a [`PriorityLock`].
///
/// This can be obtained via [`PriorityLock::split`].
///
/// [`PriorityLock`]: struct.PriorityLock.html
/// [`PriorityLock::split`]: struct.PriorityLock.html#method.split
#[derive(Debug)]
pub struct LockHalf<'a, T, P: LockPriority> {
    lock: &'a PriorityLock<T>,
    _p: PhantomData<P>,
}

impl<'a, T> LockHalf<'a, T, PLow> {
    /// Acquires the lock, granting access to `T`.
    ///
    /// This is meant to be called from a low-priority context and may be preempted by code owning
    /// the high-priority half of the lock. If the lock is already taken, this will block until it
    /// is released again.
    pub fn lock(&mut self) -> LockGuard<'a, T, PLow> {
        // This must take `&mut self` for soundness.

        self.lock.block_acquire_raw(0);
        LockGuard {
            lock: self.lock,
            _p: PhantomData,
        }
    }
}

impl<'a, T> LockHalf<'a, T, PHigh> {
    /// Tries to acquire the lock, granting access to `T`.
    ///
    /// This is meant to be called from a high-priority context that may preempt code owning the
    /// low-priority half of the lock.
    ///
    /// # Errors
    ///
    /// This operation can fail when the low-priority code already holds the lock, and is being
    /// preempted by the code calling `try_lock`. **There is no general way to recover from this**.
    /// If this is an issue, consider using a different way of sharing data between interrupts (see
    /// the [`PriorityLock`] documentation for guidance).
    ///
    /// [`PriorityLock`]: struct.PriorityLock.html
    pub fn try_lock(&mut self) -> Result<LockGuard<'a, T, PHigh>, Deadlock> {
        // This must take `&mut self` for soundness.

        self.lock.try_acquire_raw(1).map_err(|_| Deadlock {})?;
        Ok(LockGuard {
            lock: self.lock,
            _p: PhantomData,
        })
    }
}

/// A guard keeping a lock acquired until it is dropped.
pub struct LockGuard<'a, T, P: LockPriority> {
    lock: &'a PriorityLock<T>,
    _p: PhantomData<P>,
}

impl<'a, T, P: LockPriority> Deref for LockGuard<'a, T, P> {
    type Target = T;

    fn deref(&self) -> &T {
        // Safety: If the lock algorithm is correct, we have unique access to `T` here.
        unsafe { &*self.lock.data.get() }
    }
}

impl<'a, T, P: LockPriority> DerefMut for LockGuard<'a, T, P> {
    fn deref_mut(&mut self) -> &mut T {
        // Safety: If the lock algorithm is correct, we have unique access to `T` here.
        unsafe { &mut *self.lock.data.get() }
    }
}

impl<'a, T, P: LockPriority> Drop for LockGuard<'a, T, P> {
    fn drop(&mut self) {
        // Safety: We unlock only our own half of the lock, and don't access `T` anymore.
        unsafe {
            self.lock.unlock(P::INDEX);
        }
    }
}

impl<'a, T: fmt::Debug, P: LockPriority> fmt::Debug for LockGuard<'a, T, P> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&**self, f)
    }
}

impl<'a, T: fmt::Display, P: LockPriority> fmt::Display for LockGuard<'a, T, P> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&**self, f)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn simple() {
        let mut lock = PriorityLock::new(0u32);
        let (mut low, mut high) = lock.split();

        let mut low_guard = low.lock();
        *low_guard += 1;
        assert!(high.try_lock().is_err());
        drop(low_guard);

        let mut high_guard = high.try_lock().map_err(drop).unwrap();
        assert_eq!(*high_guard, 1);
        *high_guard += 1;
    }
}