aboutsummaryrefslogtreecommitdiff
path: root/pw_cpu_exception_cortex_m/entry.cc
diff options
context:
space:
mode:
Diffstat (limited to 'pw_cpu_exception_cortex_m/entry.cc')
-rw-r--r--pw_cpu_exception_cortex_m/entry.cc287
1 files changed, 287 insertions, 0 deletions
diff --git a/pw_cpu_exception_cortex_m/entry.cc b/pw_cpu_exception_cortex_m/entry.cc
new file mode 100644
index 000000000..97925f574
--- /dev/null
+++ b/pw_cpu_exception_cortex_m/entry.cc
@@ -0,0 +1,287 @@
+// Copyright 2019 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/entry.h"
+
+#include <cstdint>
+#include <cstring>
+
+#include "pw_cpu_exception/handler.h"
+#include "pw_cpu_exception_cortex_m/cpu_state.h"
+#include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h"
+#include "pw_preprocessor/compiler.h"
+
+// TODO(pwbug/311): Deprecated naming.
+PW_EXTERN_C PW_NO_PROLOGUE __attribute__((alias("pw_cpu_exception_Entry"))) void
+pw_CpuExceptionEntry(void);
+
+namespace pw::cpu_exception {
+namespace {
+
+// If the CPU fails to capture some registers, the captured struct members will
+// be populated with this value. The only registers that this value should be
+// loaded into are pc, lr, and psr when the CPU fails to push an exception
+// context frame.
+//
+// 0xFFFFFFFF is an illegal lr value, which is why it was selected for this
+// purpose. pc and psr values of 0xFFFFFFFF are dubious too, so this constant
+// is clear enough at expressing that the registers weren't properly captured.
+constexpr uint32_t kInvalidRegisterValue = 0xFFFFFFFF;
+
+// Checks exc_return in the captured CPU state to determine which stack pointer
+// was in use prior to entering the exception handler.
+bool PspWasActive(const pw_cpu_exception_State& cpu_state) {
+ return cpu_state.extended.exc_return & kExcReturnStackMask;
+}
+
+// Checks exc_return to determine if FPU state was pushed to the stack in
+// addition to the base CPU context frame.
+bool FpuStateWasPushed(const pw_cpu_exception_State& cpu_state) {
+ return !(cpu_state.extended.exc_return & kExcReturnBasicFrameMask);
+}
+
+// If the CPU successfully pushed context on exception, copy it into cpu_state.
+//
+// For more information see (See ARMv7-M Section B1.5.11, derived exceptions
+// on exception entry).
+void CloneBaseRegistersFromPsp(pw_cpu_exception_State* cpu_state) {
+ // If CPU succeeded in pushing context to PSP, copy it to the MSP.
+ if (!(cpu_state->extended.cfsr & kCfsrStkerrMask) &&
+ !(cpu_state->extended.cfsr & kCfsrMstkerrMask)) {
+ // TODO(amontanez): {r0-r3,r12} are captured in pw_cpu_exception_Entry(),
+ // so this only really needs to copy pc, lr, and psr. Could
+ // (possibly) improve speed, but would add marginally more
+ // complexity.
+ std::memcpy(&cpu_state->base,
+ reinterpret_cast<void*>(cpu_state->extended.psp),
+ sizeof(CortexMExceptionRegisters));
+ } else {
+ // If CPU context wasn't pushed to stack on exception entry, we can't
+ // recover psr, lr, and pc from exception-time. Make these values clearly
+ // invalid.
+ cpu_state->base.lr = kInvalidRegisterValue;
+ cpu_state->base.pc = kInvalidRegisterValue;
+ cpu_state->base.psr = kInvalidRegisterValue;
+ }
+}
+
+// If the CPU successfully pushed context on exception, restore it from
+// cpu_state. Otherwise, don't attempt to restore state.
+//
+// For more information see (See ARMv7-M Section B1.5.11, derived exceptions
+// on exception entry).
+void RestoreBaseRegistersToPsp(pw_cpu_exception_State* cpu_state) {
+ // If CPU succeeded in pushing context to PSP on exception entry, restore the
+ // contents of cpu_state to the CPU-pushed register frame so the CPU can
+ // continue. Otherwise, don't attempt as we'll likely end up in an escalated
+ // hard fault.
+ if (!(cpu_state->extended.cfsr & kCfsrStkerrMask) &&
+ !(cpu_state->extended.cfsr & kCfsrMstkerrMask)) {
+ std::memcpy(reinterpret_cast<void*>(cpu_state->extended.psp),
+ &cpu_state->base,
+ sizeof(CortexMExceptionRegisters));
+ }
+}
+
+// Determines the size of the CPU-pushed context frame.
+uint32_t CpuContextSize(const pw_cpu_exception_State& cpu_state) {
+ uint32_t cpu_context_size = sizeof(CortexMExceptionRegisters);
+ if (FpuStateWasPushed(cpu_state)) {
+ cpu_context_size += sizeof(CortexMExceptionRegistersFpu);
+ }
+ if (cpu_state.base.psr & kPsrExtraStackAlignBit) {
+ // Account for the extra 4-bytes the processor
+ // added to keep the stack pointer 8-byte aligned
+ cpu_context_size += 4;
+ }
+
+ return cpu_context_size;
+}
+
+// On exception entry, the Program Stack Pointer is patched to reflect the state
+// at exception-time. On exception return, it is restored to the appropriate
+// location. This calculates the delta that is used for these patch operations.
+uint32_t CalculatePspDelta(const pw_cpu_exception_State& cpu_state) {
+ // If CPU context was not pushed to program stack (because program stack
+ // wasn't in use, or an error occurred when pushing context), the PSP doesn't
+ // need to be shifted.
+ if (!PspWasActive(cpu_state) || (cpu_state.extended.cfsr & kCfsrStkerrMask) ||
+ (cpu_state.extended.cfsr & kCfsrMstkerrMask)) {
+ return 0;
+ }
+
+ return CpuContextSize(cpu_state);
+}
+
+// On exception entry, the Main Stack Pointer is patched to reflect the state
+// at exception-time. On exception return, it is restored to the appropriate
+// location. This calculates the delta that is used for these patch operations.
+uint32_t CalculateMspDelta(const pw_cpu_exception_State& cpu_state) {
+ if (PspWasActive(cpu_state)) {
+ // TODO(amontanez): Since FPU state isn't captured at this time, we ignore
+ // it when patching MSP. To add FPU capture support,
+ // delete this if block as CpuContextSize() will include
+ // FPU context size in the calculation.
+ return sizeof(CortexMExceptionRegisters) + sizeof(CortexMExtraRegisters);
+ }
+
+ return CpuContextSize(cpu_state) + sizeof(CortexMExtraRegisters);
+}
+
+} // namespace
+
+extern "C" {
+
+// Collect remaining CPU state (memory mapped registers), populate memory mapped
+// registers, and call application exception handler.
+PW_USED void pw_PackageAndHandleCpuException(
+ pw_cpu_exception_State* cpu_state) {
+ // Capture memory mapped registers.
+ cpu_state->extended.cfsr = cortex_m_cfsr;
+ cpu_state->extended.mmfar = cortex_m_mmfar;
+ cpu_state->extended.bfar = cortex_m_bfar;
+ cpu_state->extended.icsr = cortex_m_icsr;
+ cpu_state->extended.hfsr = cortex_m_hfsr;
+ cpu_state->extended.shcsr = cortex_m_shcsr;
+
+ // CPU may have automatically pushed state to the program stack. If it did,
+ // the values can be copied into in the pw_cpu_exception_State struct that is
+ // passed to HandleCpuException(). The cpu_state passed to the handler is
+ // ALWAYS stored on the main stack (MSP).
+ if (PspWasActive(*cpu_state)) {
+ CloneBaseRegistersFromPsp(cpu_state);
+ // If PSP wasn't active, this delta is 0.
+ cpu_state->extended.psp += CalculatePspDelta(*cpu_state);
+ }
+
+ // Patch captured stack pointers so they reflect the state at exception time.
+ cpu_state->extended.msp += CalculateMspDelta(*cpu_state);
+
+ // Call application-level exception handler.
+ pw_cpu_exception_HandleException(cpu_state);
+
+ // Restore program stack pointer so exception return can restore state if
+ // needed.
+ // Note: The default behavior of NOT subtracting a delta from MSP is
+ // intentional. This simplifies the assembly to pop the exception state
+ // off the main stack on exception return (since MSP currently reflects
+ // exception-time state).
+ cpu_state->extended.psp -= CalculatePspDelta(*cpu_state);
+
+ // If PSP was active and the CPU pushed a context frame, we must copy the
+ // potentially modified state from cpu_state back to the PSP so the CPU can
+ // resume execution with the modified values.
+ if (PspWasActive(*cpu_state)) {
+ // In this case, there's no need to touch the MSP as it's at the location
+ // before we entering the exception (effectively popping the state initially
+ // pushed to the main stack).
+ RestoreBaseRegistersToPsp(cpu_state);
+ } else {
+ // Since we're restoring context from MSP, we DO need to adjust MSP to point
+ // to CPU-pushed context frame so it can be properly restored.
+ // No need to adjust PSP since nothing was pushed to program stack.
+ cpu_state->extended.msp -= CpuContextSize(*cpu_state);
+ }
+}
+
+// Captures faulting CPU state on the main stack (MSP), then calls the exception
+// handlers.
+// This function should be called immediately after an exception.
+void pw_cpu_exception_Entry(void) {
+ asm volatile(
+ // clang-format off
+ // If PSP was in use at the time of exception, it's possible the CPU
+ // wasn't able to push CPU state. To be safe, this first captures scratch
+ // registers before moving forward.
+ //
+ // Stack flag is bit index 2 (0x4) of exc_return value stored in lr. When
+ // this bit is set, the Process Stack Pointer (PSP) was in use. Otherwise,
+ // the Main Stack Pointer (MSP) was in use. (See ARMv7-M Section B1.5.8
+ // for more details)
+ // The following block of assembly is equivalent to:
+ // if (lr & (1 << 2)) {
+ // msp -= sizeof(CortexMExceptionRegisters);
+ // CortexMExceptionRegisters* state =
+ // (CortexMExceptionRegisters*) msp;
+ // state->r0 = r0;
+ // state->r1 = r1;
+ // state->r2 = r2;
+ // state->r3 = r3;
+ // state->r12 = r12;
+ // }
+ //
+ " tst lr, #(1 << 2) \n"
+ " itt ne \n"
+ " subne sp, sp, %[base_state_size] \n"
+ " stmne sp, {r0-r3, r12} \n"
+
+ // Reserve stack space for additional registers. Since we're in exception
+ // handler mode, the main stack pointer is currently in use.
+ // r0 will temporarily store the end of captured_cpu_state to simplify
+ // assembly for copying additional registers.
+ " mrs r0, msp \n"
+ " sub sp, sp, %[extra_state_size] \n"
+
+ // Store GPRs to stack.
+ " stmdb r0!, {r4-r11} \n"
+
+ // Load special registers.
+ " mov r1, lr \n"
+ " mrs r2, msp \n"
+ " mrs r3, psp \n"
+ " mrs r4, control \n"
+
+ // Store special registers to stack.
+ " stmdb r0!, {r1-r4} \n"
+
+ // Store a pointer to the beginning of special registers in r4 so they can
+ // be restored later.
+ " mov r4, r0 \n"
+
+ // Restore captured_cpu_state pointer to r0. This makes adding more
+ // memory mapped registers easier in the future since they're skipped in
+ // this assembly.
+ " mrs r0, msp \n"
+
+ // Call intermediate handler that packages data.
+ " ldr r3, =pw_PackageAndHandleCpuException \n"
+ " blx r3 \n"
+
+ // Restore state and exit exception handler.
+ // Pointer to saved CPU state was stored in r4.
+ " mov r0, r4 \n"
+
+ // Restore special registers.
+ " ldm r0!, {r1-r4} \n"
+ " mov lr, r1 \n"
+ " msr control, r4 \n"
+
+ // Restore GPRs.
+ " ldm r0, {r4-r11} \n"
+
+ // Restore stack pointers.
+ " msr msp, r2 \n"
+ " msr psp, r3 \n"
+
+ // Exit exception.
+ " bx lr \n"
+ : /*output=*/
+ : /*input=*/[base_state_size]"i"(sizeof(CortexMExceptionRegisters)),
+ [extra_state_size]"i"(sizeof(CortexMExtraRegisters))
+ // clang-format on
+ );
+}
+
+} // extern "C"
+} // namespace pw::cpu_exception