blob: ee72d32a8747cac044e7485e1046c882b168a7d9 [file] [log] [blame]
// Copyright 2024 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_cpu_exception_cortex_m/exception_entry_test_util.h"
#include <cstring>
#include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h"
namespace pw::cpu_exception::cortex_m {
volatile uint32_t& cortex_m_vtor =
*reinterpret_cast<volatile uint32_t*>(0xE000ED08u);
// In-memory interrupt service routine vector table.
using InterruptVectorTable = std::aligned_storage_t<512, 512>;
InterruptVectorTable ram_vector_table;
// Begin a critical section that must not be interrupted.
// This function disables interrupts to prevent any sort of context switch until
// the critical section ends. This is done by setting PRIMASK to 1 using the cps
// instruction.
//
// Returns the state of PRIMASK before it was disabled.
inline uint32_t BeginCriticalSection() {
uint32_t previous_state;
asm volatile(
" mrs %[previous_state], primask \n"
" cpsid i \n"
// clang-format off
: /*output=*/[previous_state]"=r"(previous_state)
: /*input=*/
: /*clobbers=*/"memory"
// clang-format on
);
return previous_state;
}
// Ends a critical section.
// Restore previous previous state produced by BeginCriticalSection().
// Note: This does not always re-enable interrupts.
inline void EndCriticalSection(uint32_t previous_state) {
asm volatile(
// clang-format off
"msr primask, %0"
: /*output=*/
: /*input=*/"r"(previous_state)
: /*clobbers=*/"memory"
// clang-format on
);
}
void InstallVectorTableEntries(uint32_t* exception_entry_addr) {
uint32_t prev_state = BeginCriticalSection();
// If vector table is installed already, this is done.
if (cortex_m_vtor == reinterpret_cast<uint32_t>(&ram_vector_table)) {
EndCriticalSection(prev_state);
return;
}
// Copy table to new location since it's not guaranteed that we can write to
// the original one.
std::memcpy(&ram_vector_table,
reinterpret_cast<uint32_t*>(cortex_m_vtor),
sizeof(ram_vector_table));
// Override exception handling vector table entries.
uint32_t** interrupts = reinterpret_cast<uint32_t**>(&ram_vector_table);
interrupts[kHardFaultIsrNum] = exception_entry_addr;
// On v6-M, only HardFault is supported
#if !_PW_ARCH_ARM_V6M
interrupts[kMemFaultIsrNum] = exception_entry_addr;
interrupts[kBusFaultIsrNum] = exception_entry_addr;
interrupts[kUsageFaultIsrNum] = exception_entry_addr;
#endif
// Update Vector Table Offset Register (VTOR) to point to new vector table.
cortex_m_vtor = reinterpret_cast<uint32_t>(&ram_vector_table);
EndCriticalSection(prev_state);
}
} // namespace pw::cpu_exception::cortex_m