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

//! # Data Lock
//! 
//! Enable exclusive access to data guarded by a cross core atomic lock
//! 
//! # Example
//! ```
//! static DATA: DataLock<u32> = DataLock::new(0);
//! 
//! fn main() {
//!     if let Some(mut data) = DATA.try_lock() {
//!         *data = 20;
//!     }
//!     // once the data goes ot of scope the lock will be released
//!     if let Some(data) = DATA.try_lock() {
//!         println!("data: {}", *data);
//!     
//!         // another lock should fail inside this scope
//!         assert_eq!(DATA.try_lock(), None);
//!     }
//! }
//! ```
//! 

use core::sync::atomic::{AtomicBool, Ordering};
use core::cell::UnsafeCell;
use core::ops::{Deref, DerefMut};

/// An exclusive access lock around the given data
pub struct DataLock<T> {
    locked: AtomicBool,
    data: UnsafeCell<T>,
}

/// Result of trying to access the data using ``try_lock`` on the data lock
/// If the result goes out of scope the lock is released
pub struct TryDataLock<'a, T> {
    _data: &'a DataLock<T>,
}

impl<T> DataLock<T> {
    /// Create a new data access guarding lock
    pub fn new(value: T) -> Self {
        DataLock {
            locked: AtomicBool::new(false),
            data: UnsafeCell::new(value),
        }
    }

    /// Try to lock the guarded data for mutual exclusive access. Returns ``None`` if the lock failes
    /// or ``Some(TryDataLock)``. The actual data, the TryDataLock wraps could be conviniently accessed by
    /// dereferencing it.
    /// 
    /// # Example
    /// ```
    /// # fn doc() {
    ///     let secure_data: DataLock<u32> = DataLock::new(10);
    /// 
    ///     if let Some(data) = secure_data.try_lock() {
    ///         assert_eq!(*data, 10);
    ///     }
    /// # }
    /// ```
    pub fn try_lock(&self) -> Option<TryDataLock<T>> {
        // do the atomic operation to set the lock
        crate::disable_interrupts();
        let data_lock = if !self.locked.swap(true, Ordering::SeqCst) {
            // has been false previously means we now have the lock
            Some(TryDataLock{
                _data: self,
            })
        } else {
            // we couldn't set the lock
            None
        };
        crate::re_enable_interrupts();
        data_lock
    }
}

// when the TryDataLock is dropped release the owning lock
impl<T> Drop for TryDataLock<'_, T> {
    fn drop(&mut self) {
        self._data.locked.swap(false, Ordering::SeqCst);
    }
}

// dereferencing the value contained in the TryDataLock
// this ok as the TryDataLock does only exist if the exclusive access to the data could
// be ensures. Therefore also only one ``TryDataLock`` could ever exist for one specific ``DataLock``, which makes it
// safe to return immutable and mutable references.
impl <T> Deref for TryDataLock<'_, T> {
    type Target = T;

    fn deref(&self) -> &T {
        unsafe { &*self._data.data.get() }
    }
}

impl <T> DerefMut for TryDataLock<'_, T> {
    fn deref_mut(&mut self) -> &mut T {
        unsafe { &mut *self._data.data.get() }
    }
}