ruspiro_lock/sync/
spinlock.rs

1/***********************************************************************************************************************
2 * Copyright (c) 2020 by the authors
3 *
4 * Author: André Borrmann <pspwizard@gmx.de>
5 * License: Apache License 2.0 / MIT
6 **********************************************************************************************************************/
7
8//! # Spinlock
9//! Providing simple atomic Spinlock. This can be used to ensure cross core atomic access to data that is typically
10//! shared between them. For example MMIO mapped registers that allow access to peripherals. Please note that usage
11//! of Spinlocks on Raspberry Pi is only safe if the MMU has ben configured properly. Otherwise the cores trying to
12//! aquire a lock will just hang, even if the lock would be available to them.
13//!
14//! # Example
15//! ```
16//! use ruspiro_lock::sync::Spinlock;
17//!
18//! static LOCK: Spinlock = Spinlock::new();
19//!
20//! fn main () {
21//!     LOCK.aquire(); // will only return if the lock could be set
22//!     // do something
23//!
24//!     LOCK.release(); // releasing the lock
25//! }
26//! ```
27use core::arch::asm;
28use core::sync::atomic::{AtomicBool, Ordering};
29
30/// A blocking cross core lock to guarantee mutual exclusive access. While this lock might block other cores
31/// to continue processing this lock should be held as short as possible. Also care shall be taken
32/// while using this lock within interrupt handlers, as this might lead to deadlock situations if the
33/// lock holding core is interrupted and the interrupt is also trying to aquire the same lock.
34#[derive(Debug)]
35#[repr(C, align(16))]
36pub struct Spinlock {
37  flag: AtomicBool,
38}
39
40impl Spinlock {
41  /// Create a new Spinlock. To ensure it is shared between cores, it's typically assigned to a static variable
42  /// # Example
43  /// ```
44  /// # use ruspiro_lock::sync::Spinlock;
45  /// static LOCK: Spinlock = Spinlock::new();
46  /// ```
47  pub const fn new() -> Spinlock {
48    Spinlock {
49      flag: AtomicBool::new(false),
50    }
51  }
52
53  /// Aquire a spinlock. This will block the current core until the lock could be aquired.
54  /// # Example
55  /// ```no_run
56  /// # use ruspiro_lock::sync::Spinlock;
57  /// static LOCK: Spinlock = Spinlock::new();
58  /// # fn main() {
59  ///     LOCK.aquire();
60  ///     // execution continues only if the lock could be aquired
61  /// # }
62  /// ```
63  #[inline]
64  pub fn aquire(&self) {
65    // set the atomic value to true if it has been false before (set the lock)
66    while self
67      .flag
68      .compare_exchange(false, true, Ordering::SeqCst, Ordering::Acquire)
69      .is_err()
70    {}
71
72    #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
73    unsafe {
74      // dmb required before allow access to the protected resource, see:
75      // http://infocenter.arm.com/help/topic/com.arm.doc.dht0008a/DHT0008A_arm_synchronization_primitives.pdf
76      asm!("dmb sy");
77    }
78  }
79
80  /// Release an aquired spinlock.
81  /// # Example
82  /// ```no_run
83  /// # use ruspiro_lock::sync::Spinlock;
84  /// static LOCK: Spinlock = Spinlock::new();
85  /// # fn main() {
86  ///     LOCK.release();
87  /// # }
88  /// ```
89  #[inline]
90  pub fn release(&self) {
91    self.flag.store(false, Ordering::SeqCst);
92
93    #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
94    unsafe {
95      // dmb required before allow access to the protected resource, see:
96      // http://infocenter.arm.com/help/topic/com.arm.doc.dht0008a/DHT0008A_arm_synchronization_primitives.pdf
97      asm!("dmb sy");
98      // also raise a signal to indicate the spinlock has been changed (this trigger all WFE's to continue
99      // processing) but do data syncronisation barrier upfront to ensure any data updates has been finished
100      asm!(
101        "dsb sy
102                 sev"
103      );
104    }
105  }
106}