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
//! Routines for managing interrupts.
//! Based on:
//! - <https://github.com/avr-rust/ruduino/blob/master/src/interrupt.rs>
//! - <https://docs.rs/bare-metal/0.2.5/src/bare_metal/lib.rs.html>

use core::{arch::asm, marker::PhantomData};

pub mod mutex;
use mutex::Mutex;

/// Atomic counter of critical sections to avoid problems when `without_interrupts` is used in
/// nested function calls.
#[cfg(not(feature = "unsafe-no-critical-section-count"))]
static CRITICAL_SECTION_COUNTER: Mutex<usize> = Mutex::new(0);

/// Helper struct that automatically restores interrupts on drop. The wrapped `PhantomData` creates
/// a private field to ensure that this struct cannot be initialized from outside of this module
/// without using its `unsafe` initializer function `new`. The recommended use to enter a
/// `CriticalSection` is to pass a closure to `without_interrupts`.
///
/// When the feature `unsafe-no-critical-section-count` is disabled, this implementation is also
/// safe w.r.t. nested calls of `without_interrupts`. This is achieved by counting how many
/// `CriticalSection`s were entered, and only enabling device interrupts once the last
/// `CriticalSection` is exited. However, as these checks incur a small runtime overhead, they can
/// be disabled with the feature `unsafe-no-critical-section-count`. Note that, for execution
/// consistency, a user must then ensure that `without_interrupts` will never be nested!
pub struct CriticalSection(PhantomData<()>);

impl CriticalSection {
    /// Upon entering any `CriticalSection`, disable global device interrupts.
    ///
    /// # Safety
    /// When the feature `unsafe-no-critical-section-count` is disabled, this implementation is also
    /// safe w.r.t. nested `CriticalSection`s, e.g., by nesting calls to `without_interrupts`. This
    /// is achieved by counting how many `CriticalSection`s were entered, and only enabling device
    /// interrupts once the last `CriticalSection` is exited. However, as these checks incur a small
    /// runtime overhead, they can be disabled with the feature `unsafe-no-critical-section-count`.
    /// Note that, for execution consistency, a user must then ensure that `CriticalSection`s will
    /// never be nested!
    #[inline(always)]
    pub unsafe fn new() -> Self {
        // first, deactivate interrupts
        asm!("CLI");

        // next, create the new `CriticalSection`
        let cs = CriticalSection(PhantomData);

        // now, in guaranteed single-threaded mode, increase number of `CriticalSection`s
        #[cfg(not(feature = "unsafe-no-critical-section-count"))]
        CRITICAL_SECTION_COUNTER.lock(&cs).update(|x| x + 1);

        cs
    }
}

impl Drop for CriticalSection {
    /// Upon dropping the last `CriticalSection`, enable global device interrupts.
    #[inline(always)]
    fn drop(&mut self) {
        #[cfg(not(feature = "unsafe-no-critical-section-count"))]
        CRITICAL_SECTION_COUNTER.lock(self).update(|x| {
            if x == 1 {
                unsafe { asm!("SEI") }
            }
            x - 1
        });

        #[cfg(feature = "unsafe-no-critical-section-count")]
        unsafe {
            asm!("SEI")
        }
    }
}

/// Executes a closure, disabling interrupts until its completion. Introduces a `CriticalSection`
/// that allows to access shared data structures via the guards provided in the `mutex` module.
///
/// Restores interrupts after the closure has completed execution.
#[inline(always)]
pub fn without_interrupts<F, T>(f: F) -> T
where
    F: FnOnce(&mut CriticalSection) -> T,
{
    // entering a `CriticalSection` is unsafe
    let mut critical_section = unsafe { CriticalSection::new() };

    // run the given closure with a unique reference to the `CriticalSection` to allow
    // accessing a `Mutex`
    let result = f(&mut critical_section);

    // explicitly ensure that the `CriticalSection` is left after the closure has been processed
    drop(critical_section);

    // return whatever the closure yielded
    result
}