xref: /aosp_15_r20/external/pigweed/pw_sync_threadx/interrupt_spin_lock.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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