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
/*********************************************************************************************************************** 
 * Copyright (c) 2019 by the authors
 * 
 * Author: André Borrmann 
 * License: Apache License 2.0
 **********************************************************************************************************************/

//! # Semaphore implementation. It uses atomic memory locks to ensure the semaphore is exclusively accessed while checking
//! or updating it's value. On Raspbarry Pi this will only work if the MMU has been properly configured. Otherwise those
//! operations may just hang.
//! 
//! # Example
//! ```
//! static SEMA: Semaphore = Semaphore::new(1);
//! 
//! fn main () {
//!     SEMA.down(); // will only return if the counter could be decreased
//!     // do something
//! 
//!     SEMA.up(); // increase the counter for another usage
//! }
//! ```
use core::sync::atomic::{AtomicBool, Ordering, fence};
use core::cell::Cell;

#[derive(Debug)]
pub struct Semaphore {
    flag: AtomicBool,
    count: Cell<u32>,
}

impl Semaphore {
    /// Instantiate a new semaphore with a given initial value
    /// # Example
    /// ```
    /// let mut sema = Semaphore::new(5); // semaphore could be used/aquired 5 times
    /// ```
    pub const fn new(initial: u32) -> Semaphore {
        Semaphore {
            flag: AtomicBool::new(false),
            count: Cell::new(initial),
        }
    }

    /// increase the inner count of a semaphore allowing it to be used as many times as the inner counters value
    /// 
    /// # Example
    /// ```
    /// let mut sema = Semaphore::new(0);
    /// 
    /// sema.up(); // the counter of the semaphore will be increased
    /// ```
    pub fn up(&self) {
        // ensure atomic access to the count value so it is not updated from other cores while updating
        // we need to deactivate interrupts as this wait should never beeing interrupted
        // otherwise it could lead to deadlocks
        crate::disable_interrupts();
        while self.flag.compare_and_swap(false, true, Ordering::Relaxed) != false { }
        fence(Ordering::Acquire);
                
        let c = self.count.get();
        self.count.set(c + 1);
        // release the atomic access
        self.flag.store(false, Ordering::Release);
        crate::re_enable_interrupts();
    }

    /// decrease the inner count of a semaphore. This blocks the current core if the current count is 0
    /// and could not beeing decreased. For an unblocking operation use [Semaphore::try_down]
    /// 
    /// # Example
    /// ```
    /// let sema = Semaphore::new(0);
    /// 
    /// sema.down();
    /// // if we reache this line, we have used the semaphore and decreased the counter by 1
    /// ```
    pub fn down(&self) {
        loop {
            if self.try_down().is_ok() {
                return;
            }
            // a small hack to force some cpu cycles to pass before the next try
            // may be a timer wait in the future?
            for _ in 0..100 {
                unsafe { asm!("nop") };
            }
        }
    }

    /// try to decrease a semaphore for usage. Returns [Ok()] if the semaphore could be used.
    /// 
    /// # Example
    /// ```
    /// let sema = Semaphore::new(0);
    /// 
    /// if sema.try_down().is_ok() {
    ///     // do something... the counter of the semaphore has been decreased by 1
    /// }
    /// ```
    pub fn try_down(&self) -> Result<(), &'static str> {
        // we need to deactivate interrupts as this wait should never beeing interrupted
        // otherwise it could lead to deadlocks
        crate::disable_interrupts();
        while self.flag.compare_and_swap(false, true, Ordering::Relaxed) != false { }
        fence(Ordering::Acquire);
        
        // try to decrease the counter
        let c = self.count.get();
        let success = if c > 0 {
            self.count.set(c - 1);
            true
        } else {
            false
        };
        // release the atomic access
        self.flag.store(false, Ordering::Release);
        crate::re_enable_interrupts();
                
        if success {
            Ok(())
        } else {
            Err("unable to use the semaphore, counter is less than 1")
        }
    }
}