1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_sync/interrupt_spin_lock.h"
16
17 #include "pw_assert/check.h"
18 #include "pw_interrupt/context.h"
19 #include "tx_api.h"
20
21 namespace pw::sync {
22 namespace {
23
24 using State = backend::NativeInterruptSpinLock::State;
25
26 } // namespace
27
lock()28 void InterruptSpinLock::lock() {
29 // In order to be pw::sync::InterruptSpinLock compliant, mask the interrupts
30 // before attempting to grab the internal spin lock.
31 native_type_.saved_interrupt_mask = tx_interrupt_control(TX_INT_DISABLE);
32
33 const bool in_interrupt = interrupt::InInterruptContext();
34
35 // Disable thread switching to ensure kernel APIs cannot switch to other
36 // threads which could then end up deadlocking recursively on this same lock.
37 if (!in_interrupt) {
38 TX_THREAD* current_thread = tx_thread_identify();
39 // During init, i.e. tx_application_define, there may not be a thread yet.
40 if (current_thread != nullptr) {
41 // Disable thread switching by raising the preemption threshold to the
42 // highest priority value of 0.
43 UINT preemption_success = tx_thread_preemption_change(
44 tx_thread_identify(), 0, &native_type_.saved_preemption_threshold);
45 PW_DCHECK_UINT_EQ(
46 TX_SUCCESS, preemption_success, "Failed to disable thread switching");
47 }
48 }
49
50 // This implementation is not set up to support SMP, meaning we cannot
51 // deadlock here due to the global interrupt lock, so we crash on recursion
52 // on a specific spinlock instead.
53 PW_DCHECK_UINT_EQ(native_type_.state,
54 State::kUnlocked,
55 "Recursive InterruptSpinLock::lock() detected");
56
57 native_type_.state =
58 in_interrupt ? State::kLockedFromInterrupt : State::kLockedFromThread;
59 }
60
unlock()61 void InterruptSpinLock::unlock() {
62 const bool in_interrupt = interrupt::InInterruptContext();
63
64 const State expected_state =
65 in_interrupt ? State::kLockedFromInterrupt : State::kLockedFromThread;
66 PW_CHECK_UINT_EQ(
67 native_type_.state,
68 expected_state,
69 "InterruptSpinLock::unlock() was called from a different context "
70 "compared to the lock()");
71
72 native_type_.state = State::kUnlocked;
73
74 if (!in_interrupt) {
75 TX_THREAD* current_thread = tx_thread_identify();
76 // During init, i.e. tx_application_define, there may not be a thread yet.
77 if (current_thread != nullptr) {
78 // Restore thread switching.
79 UINT unused = 0;
80 UINT preemption_success =
81 tx_thread_preemption_change(tx_thread_identify(),
82 native_type_.saved_preemption_threshold,
83 &unused);
84 PW_DCHECK_UINT_EQ(
85 TX_SUCCESS, preemption_success, "Failed to restore thread switching");
86 }
87 }
88 tx_interrupt_control(native_type_.saved_interrupt_mask);
89 }
90
91 } // namespace pw::sync
92