aboutsummaryrefslogtreecommitdiff
path: root/pw_sync/public/pw_sync/interrupt_spin_lock.h
blob: 7726c92a886c9267ea1d1df5dbaf14a07b42b56e (plain)
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
// Copyright 2020 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once

#include <stdbool.h>

#include "pw_preprocessor/util.h"
#include "pw_sync/lock_annotations.h"

#ifdef __cplusplus

#include "pw_sync/virtual_basic_lockable.h"
#include "pw_sync_backend/interrupt_spin_lock_native.h"

namespace pw::sync {

/// The `InterruptSpinLock` is a synchronization primitive that can be used to
/// protect shared data from being simultaneously accessed by multiple threads
/// and/or interrupts as a targeted global lock, with the exception of
/// Non-Maskable Interrupts (NMIs).
/// It offers exclusive, non-recursive ownership semantics where IRQs up to a
/// backend defined level of "NMIs" will be masked to solve priority-inversion.
///
/// @note This `InterruptSpinLock` relies on built-in local interrupt masking to
///       make it interrupt safe without requiring the caller to separately mask
///       and unmask interrupts when using this primitive.
///
/// Unlike global interrupt locks, this also works safely and efficiently on SMP
/// systems. On systems which are not SMP, spinning is not required and it's
/// possible that only interrupt masking occurs but some state may still be used
/// to detect recursion.
///
/// This entire API is IRQ safe, but NOT NMI safe.
///
/// @b Precondition: Code that holds a specific `InterruptSpinLock` must not try
/// to re-acquire it. However, it is okay to nest distinct spinlocks.
class PW_LOCKABLE("pw::sync::InterruptSpinLock") InterruptSpinLock {
 public:
  using native_handle_type = backend::NativeInterruptSpinLockHandle;

  constexpr InterruptSpinLock();
  ~InterruptSpinLock() = default;
  InterruptSpinLock(const InterruptSpinLock&) = delete;
  InterruptSpinLock(InterruptSpinLock&&) = delete;
  InterruptSpinLock& operator=(const InterruptSpinLock&) = delete;
  InterruptSpinLock& operator=(InterruptSpinLock&&) = delete;

  /// Locks the spinlock, blocking indefinitely. Failures are fatal.
  ///
  /// @b Precondition: Recursive locking is undefined behavior.
  void lock() PW_EXCLUSIVE_LOCK_FUNCTION();

  /// Tries to lock the spinlock in a non-blocking manner.
  /// Returns true if the spinlock was successfully acquired.
  ///
  /// @b Precondition: Recursive locking is undefined behavior.
  bool try_lock() PW_EXCLUSIVE_TRYLOCK_FUNCTION(true);

  /// Unlocks the spinlock. Failures are fatal.
  ///
  /// @b Precondition:
  ///   The spinlock is held by the caller.
  void unlock() PW_UNLOCK_FUNCTION();

  native_handle_type native_handle();

 private:
  /// This may be a wrapper around a native type with additional members.
  backend::NativeInterruptSpinLock native_type_;
};

class PW_LOCKABLE("pw::sync::VirtualInterruptSpinLock")
    VirtualInterruptSpinLock final : public VirtualBasicLockable {
 public:
  VirtualInterruptSpinLock() = default;

  VirtualInterruptSpinLock(const VirtualInterruptSpinLock&) = delete;
  VirtualInterruptSpinLock(VirtualInterruptSpinLock&&) = delete;
  VirtualInterruptSpinLock& operator=(const VirtualInterruptSpinLock&) = delete;
  VirtualInterruptSpinLock& operator=(VirtualInterruptSpinLock&&) = delete;

  InterruptSpinLock& interrupt_spin_lock() { return interrupt_spin_lock_; }

 private:
  void DoLockOperation(Operation operation) override
      PW_NO_LOCK_SAFETY_ANALYSIS {
    switch (operation) {
      case Operation::kLock:
        return interrupt_spin_lock_.lock();

      case Operation::kUnlock:
      default:
        return interrupt_spin_lock_.unlock();
    }
  }

  InterruptSpinLock interrupt_spin_lock_;
};

}  // namespace pw::sync

#include "pw_sync_backend/interrupt_spin_lock_inline.h"

using pw_sync_InterruptSpinLock = pw::sync::InterruptSpinLock;

#else  // !defined(__cplusplus)

typedef struct pw_sync_InterruptSpinLock pw_sync_InterruptSpinLock;

#endif  // __cplusplus

PW_EXTERN_C_START

/// Invokes the `InterruptSpinLock::lock` member function on the given
/// `interrupt_spin_lock`.
void pw_sync_InterruptSpinLock_Lock(pw_sync_InterruptSpinLock* spin_lock)
    PW_NO_LOCK_SAFETY_ANALYSIS;

/// Invokes the `InterruptSpinLock::try_lock` member function on the given
/// `interrupt_spin_lock`.
bool pw_sync_InterruptSpinLock_TryLock(pw_sync_InterruptSpinLock* spin_lock)
    PW_NO_LOCK_SAFETY_ANALYSIS;

/// Invokes the `InterruptSpinLock::unlock` member function on the given
/// `interrupt_spin_lock`.
void pw_sync_InterruptSpinLock_Unlock(pw_sync_InterruptSpinLock* spin_lock)
    PW_NO_LOCK_SAFETY_ANALYSIS;

PW_EXTERN_C_END