blob: 96b39ca15ccdcba17d1cf29c9c38d576df4ea9ee [file] [log] [blame]
// 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.
#include "pw_sync/interrupt_spin_lock.h"
#include "pw_assert/check.h"
#include "pw_interrupt/context.h"
#include "tx_api.h"
namespace pw::sync {
namespace {
using State = backend::NativeInterruptSpinLock::State;
} // namespace
void InterruptSpinLock::lock() {
// In order to be pw::sync::InterruptSpinLock compliant, mask the interrupts
// before attempting to grab the internal spin lock.
native_type_.saved_interrupt_mask = tx_interrupt_control(TX_INT_DISABLE);
const bool in_interrupt = interrupt::InInterruptContext();
// Disable thread switching to ensure kernel APIs cannot switch to other
// threads which could then end up deadlocking recursively on this same lock.
if (!in_interrupt) {
TX_THREAD* current_thread = tx_thread_identify();
// During init, i.e. tx_application_define, there may not be a thread yet.
if (current_thread != nullptr) {
// Disable thread switching by raising the preemption threshold to the
// highest priority value of 0.
UINT preemption_success = tx_thread_preemption_change(
tx_thread_identify(), 0, &native_type_.saved_preemption_threshold);
PW_DCHECK_UINT_EQ(
TX_SUCCESS, preemption_success, "Failed to disable thread switching");
}
}
// This implementation is not set up to support SMP, meaning we cannot
// deadlock here due to the global interrupt lock, so we crash on recursion
// on a specific spinlock instead.
PW_CHECK_UINT_EQ(native_type_.state,
State::kUnlocked,
"Recursive InterruptSpinLock::lock() detected");
native_type_.state =
in_interrupt ? State::kLockedFromInterrupt : State::kLockedFromThread;
}
bool InterruptSpinLock::try_lock() {
// In order to be pw::sync::InterruptSpinLock compliant, mask the interrupts
// before attempting to grab the internal spin lock.
const UINT saved_interrupt_mask = tx_interrupt_control(TX_INT_DISABLE);
const bool in_interrupt = interrupt::InInterruptContext();
// Disable thread switching to ensure kernel APIs cannot switch to other
// threads which could then end up deadlocking recursively on this same lock.
if (!in_interrupt) {
TX_THREAD* current_thread = tx_thread_identify();
// During init, i.e. tx_application_define, there may not be a thread yet.
if (current_thread != nullptr) {
// Disable thread switching by raising the preemption threshold to the
// highest priority value of 0.
UINT preemption_success = tx_thread_preemption_change(
tx_thread_identify(), 0, &native_type_.saved_preemption_threshold);
PW_DCHECK_UINT_EQ(
TX_SUCCESS, preemption_success, "Failed to disable thread switching");
}
}
if (native_type_.state != State::kUnlocked) {
// Already locked, restore interrupts and thread switching and bail out.
if (!interrupt::InInterruptContext()) {
// Restore thread switching.
UINT unused = 0;
UINT preemption_success =
tx_thread_preemption_change(tx_thread_identify(),
native_type_.saved_preemption_threshold,
&unused);
PW_DCHECK_UINT_EQ(
TX_SUCCESS, preemption_success, "Failed to restore thread switching");
}
tx_interrupt_control(saved_interrupt_mask);
return false;
}
native_type_.saved_interrupt_mask = saved_interrupt_mask;
native_type_.state =
in_interrupt ? State::kLockedFromInterrupt : State::kLockedFromThread;
return true;
}
void InterruptSpinLock::unlock() {
const bool in_interrupt = interrupt::InInterruptContext();
const State expected_state =
in_interrupt ? State::kLockedFromInterrupt : State::kLockedFromThread;
PW_CHECK_UINT_EQ(
native_type_.state,
expected_state,
"InterruptSpinLock::unlock() was called from a different context "
"compared to the lock()");
native_type_.state = State::kUnlocked;
if (!in_interrupt) {
TX_THREAD* current_thread = tx_thread_identify();
// During init, i.e. tx_application_define, there may not be a thread yet.
if (current_thread != nullptr) {
// Restore thread switching.
UINT unused = 0;
UINT preemption_success =
tx_thread_preemption_change(tx_thread_identify(),
native_type_.saved_preemption_threshold,
&unused);
PW_DCHECK_UINT_EQ(
TX_SUCCESS, preemption_success, "Failed to restore thread switching");
}
}
tx_interrupt_control(native_type_.saved_interrupt_mask);
}
} // namespace pw::sync