blob: 97925f5740b2fdbe455200168906b10e146fcc68 [file] [log] [blame]
Armando Montanez5104cd62019-12-10 14:36:43 -08001// Copyright 2019 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
Armando Montanez356bf972020-06-04 10:35:55 -070015#include "pw_cpu_exception/entry.h"
Armando Montanez5104cd62019-12-10 14:36:43 -080016
Armando Montanez356bf972020-06-04 10:35:55 -070017#include <cstdint>
18#include <cstring>
19
20#include "pw_cpu_exception/handler.h"
Armando Montaneza9ca9992021-01-26 17:06:10 -080021#include "pw_cpu_exception_cortex_m/cpu_state.h"
22#include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h"
Armando Montanez5104cd62019-12-10 14:36:43 -080023#include "pw_preprocessor/compiler.h"
24
Armando Montaneza9ca9992021-01-26 17:06:10 -080025// TODO(pwbug/311): Deprecated naming.
26PW_EXTERN_C PW_NO_PROLOGUE __attribute__((alias("pw_cpu_exception_Entry"))) void
27pw_CpuExceptionEntry(void);
28
Armando Montanez5104cd62019-12-10 14:36:43 -080029namespace pw::cpu_exception {
30namespace {
31
Armando Montanez5104cd62019-12-10 14:36:43 -080032// If the CPU fails to capture some registers, the captured struct members will
33// be populated with this value. The only registers that this value should be
34// loaded into are pc, lr, and psr when the CPU fails to push an exception
35// context frame.
36//
37// 0xFFFFFFFF is an illegal lr value, which is why it was selected for this
38// purpose. pc and psr values of 0xFFFFFFFF are dubious too, so this constant
39// is clear enough at expressing that the registers weren't properly captured.
40constexpr uint32_t kInvalidRegisterValue = 0xFFFFFFFF;
41
42// Checks exc_return in the captured CPU state to determine which stack pointer
43// was in use prior to entering the exception handler.
Armando Montaneza9ca9992021-01-26 17:06:10 -080044bool PspWasActive(const pw_cpu_exception_State& cpu_state) {
Armando Montanez5104cd62019-12-10 14:36:43 -080045 return cpu_state.extended.exc_return & kExcReturnStackMask;
46}
47
48// Checks exc_return to determine if FPU state was pushed to the stack in
49// addition to the base CPU context frame.
Armando Montaneza9ca9992021-01-26 17:06:10 -080050bool FpuStateWasPushed(const pw_cpu_exception_State& cpu_state) {
Armando Montanez5104cd62019-12-10 14:36:43 -080051 return !(cpu_state.extended.exc_return & kExcReturnBasicFrameMask);
52}
53
54// If the CPU successfully pushed context on exception, copy it into cpu_state.
55//
56// For more information see (See ARMv7-M Section B1.5.11, derived exceptions
57// on exception entry).
Armando Montaneza9ca9992021-01-26 17:06:10 -080058void CloneBaseRegistersFromPsp(pw_cpu_exception_State* cpu_state) {
Armando Montanez5104cd62019-12-10 14:36:43 -080059 // If CPU succeeded in pushing context to PSP, copy it to the MSP.
Armando Montanezcddab2d2021-01-11 19:11:05 -080060 if (!(cpu_state->extended.cfsr & kCfsrStkerrMask) &&
61 !(cpu_state->extended.cfsr & kCfsrMstkerrMask)) {
Armando Montaneza9ca9992021-01-26 17:06:10 -080062 // TODO(amontanez): {r0-r3,r12} are captured in pw_cpu_exception_Entry(),
Armando Montanez5104cd62019-12-10 14:36:43 -080063 // so this only really needs to copy pc, lr, and psr. Could
64 // (possibly) improve speed, but would add marginally more
65 // complexity.
66 std::memcpy(&cpu_state->base,
67 reinterpret_cast<void*>(cpu_state->extended.psp),
Armando Montaneza9ca9992021-01-26 17:06:10 -080068 sizeof(CortexMExceptionRegisters));
Armando Montanez5104cd62019-12-10 14:36:43 -080069 } else {
70 // If CPU context wasn't pushed to stack on exception entry, we can't
71 // recover psr, lr, and pc from exception-time. Make these values clearly
72 // invalid.
73 cpu_state->base.lr = kInvalidRegisterValue;
74 cpu_state->base.pc = kInvalidRegisterValue;
75 cpu_state->base.psr = kInvalidRegisterValue;
76 }
77}
78
79// If the CPU successfully pushed context on exception, restore it from
80// cpu_state. Otherwise, don't attempt to restore state.
81//
82// For more information see (See ARMv7-M Section B1.5.11, derived exceptions
83// on exception entry).
Armando Montaneza9ca9992021-01-26 17:06:10 -080084void RestoreBaseRegistersToPsp(pw_cpu_exception_State* cpu_state) {
Armando Montanez5104cd62019-12-10 14:36:43 -080085 // If CPU succeeded in pushing context to PSP on exception entry, restore the
86 // contents of cpu_state to the CPU-pushed register frame so the CPU can
87 // continue. Otherwise, don't attempt as we'll likely end up in an escalated
88 // hard fault.
Armando Montanezcddab2d2021-01-11 19:11:05 -080089 if (!(cpu_state->extended.cfsr & kCfsrStkerrMask) &&
90 !(cpu_state->extended.cfsr & kCfsrMstkerrMask)) {
Armando Montanez5104cd62019-12-10 14:36:43 -080091 std::memcpy(reinterpret_cast<void*>(cpu_state->extended.psp),
92 &cpu_state->base,
Armando Montaneza9ca9992021-01-26 17:06:10 -080093 sizeof(CortexMExceptionRegisters));
Armando Montanez5104cd62019-12-10 14:36:43 -080094 }
95}
96
97// Determines the size of the CPU-pushed context frame.
Armando Montaneza9ca9992021-01-26 17:06:10 -080098uint32_t CpuContextSize(const pw_cpu_exception_State& cpu_state) {
99 uint32_t cpu_context_size = sizeof(CortexMExceptionRegisters);
Armando Montanez5104cd62019-12-10 14:36:43 -0800100 if (FpuStateWasPushed(cpu_state)) {
Armando Montaneza9ca9992021-01-26 17:06:10 -0800101 cpu_context_size += sizeof(CortexMExceptionRegistersFpu);
Armando Montanez5104cd62019-12-10 14:36:43 -0800102 }
103 if (cpu_state.base.psr & kPsrExtraStackAlignBit) {
104 // Account for the extra 4-bytes the processor
105 // added to keep the stack pointer 8-byte aligned
106 cpu_context_size += 4;
107 }
108
109 return cpu_context_size;
110}
111
112// On exception entry, the Program Stack Pointer is patched to reflect the state
113// at exception-time. On exception return, it is restored to the appropriate
114// location. This calculates the delta that is used for these patch operations.
Armando Montaneza9ca9992021-01-26 17:06:10 -0800115uint32_t CalculatePspDelta(const pw_cpu_exception_State& cpu_state) {
Armando Montanez5104cd62019-12-10 14:36:43 -0800116 // If CPU context was not pushed to program stack (because program stack
117 // wasn't in use, or an error occurred when pushing context), the PSP doesn't
118 // need to be shifted.
Armando Montanezcddab2d2021-01-11 19:11:05 -0800119 if (!PspWasActive(cpu_state) || (cpu_state.extended.cfsr & kCfsrStkerrMask) ||
120 (cpu_state.extended.cfsr & kCfsrMstkerrMask)) {
Armando Montanez5104cd62019-12-10 14:36:43 -0800121 return 0;
122 }
123
124 return CpuContextSize(cpu_state);
125}
126
127// On exception entry, the Main Stack Pointer is patched to reflect the state
128// at exception-time. On exception return, it is restored to the appropriate
129// location. This calculates the delta that is used for these patch operations.
Armando Montaneza9ca9992021-01-26 17:06:10 -0800130uint32_t CalculateMspDelta(const pw_cpu_exception_State& cpu_state) {
Armando Montanez5104cd62019-12-10 14:36:43 -0800131 if (PspWasActive(cpu_state)) {
132 // TODO(amontanez): Since FPU state isn't captured at this time, we ignore
133 // it when patching MSP. To add FPU capture support,
134 // delete this if block as CpuContextSize() will include
135 // FPU context size in the calculation.
Armando Montaneza9ca9992021-01-26 17:06:10 -0800136 return sizeof(CortexMExceptionRegisters) + sizeof(CortexMExtraRegisters);
Armando Montanez5104cd62019-12-10 14:36:43 -0800137 }
138
Armando Montaneza9ca9992021-01-26 17:06:10 -0800139 return CpuContextSize(cpu_state) + sizeof(CortexMExtraRegisters);
Armando Montanez5104cd62019-12-10 14:36:43 -0800140}
141
142} // namespace
143
144extern "C" {
145
146// Collect remaining CPU state (memory mapped registers), populate memory mapped
147// registers, and call application exception handler.
Armando Montaneza9ca9992021-01-26 17:06:10 -0800148PW_USED void pw_PackageAndHandleCpuException(
149 pw_cpu_exception_State* cpu_state) {
Armando Montanez5104cd62019-12-10 14:36:43 -0800150 // Capture memory mapped registers.
Armando Montaneza9ca9992021-01-26 17:06:10 -0800151 cpu_state->extended.cfsr = cortex_m_cfsr;
152 cpu_state->extended.mmfar = cortex_m_mmfar;
153 cpu_state->extended.bfar = cortex_m_bfar;
154 cpu_state->extended.icsr = cortex_m_icsr;
155 cpu_state->extended.hfsr = cortex_m_hfsr;
156 cpu_state->extended.shcsr = cortex_m_shcsr;
Armando Montanez5104cd62019-12-10 14:36:43 -0800157
158 // CPU may have automatically pushed state to the program stack. If it did,
Armando Montaneza9ca9992021-01-26 17:06:10 -0800159 // the values can be copied into in the pw_cpu_exception_State struct that is
Armando Montanez356bf972020-06-04 10:35:55 -0700160 // passed to HandleCpuException(). The cpu_state passed to the handler is
161 // ALWAYS stored on the main stack (MSP).
Armando Montanez5104cd62019-12-10 14:36:43 -0800162 if (PspWasActive(*cpu_state)) {
163 CloneBaseRegistersFromPsp(cpu_state);
164 // If PSP wasn't active, this delta is 0.
165 cpu_state->extended.psp += CalculatePspDelta(*cpu_state);
166 }
167
168 // Patch captured stack pointers so they reflect the state at exception time.
169 cpu_state->extended.msp += CalculateMspDelta(*cpu_state);
170
171 // Call application-level exception handler.
Armando Montaneza9ca9992021-01-26 17:06:10 -0800172 pw_cpu_exception_HandleException(cpu_state);
Armando Montanez5104cd62019-12-10 14:36:43 -0800173
174 // Restore program stack pointer so exception return can restore state if
175 // needed.
176 // Note: The default behavior of NOT subtracting a delta from MSP is
177 // intentional. This simplifies the assembly to pop the exception state
178 // off the main stack on exception return (since MSP currently reflects
179 // exception-time state).
180 cpu_state->extended.psp -= CalculatePspDelta(*cpu_state);
181
182 // If PSP was active and the CPU pushed a context frame, we must copy the
183 // potentially modified state from cpu_state back to the PSP so the CPU can
184 // resume execution with the modified values.
185 if (PspWasActive(*cpu_state)) {
186 // In this case, there's no need to touch the MSP as it's at the location
187 // before we entering the exception (effectively popping the state initially
188 // pushed to the main stack).
189 RestoreBaseRegistersToPsp(cpu_state);
190 } else {
191 // Since we're restoring context from MSP, we DO need to adjust MSP to point
192 // to CPU-pushed context frame so it can be properly restored.
193 // No need to adjust PSP since nothing was pushed to program stack.
194 cpu_state->extended.msp -= CpuContextSize(*cpu_state);
195 }
196}
197
198// Captures faulting CPU state on the main stack (MSP), then calls the exception
199// handlers.
200// This function should be called immediately after an exception.
Armando Montaneza9ca9992021-01-26 17:06:10 -0800201void pw_cpu_exception_Entry(void) {
Armando Montanez5104cd62019-12-10 14:36:43 -0800202 asm volatile(
Armando Montaneza9ca9992021-01-26 17:06:10 -0800203 // clang-format off
Armando Montanez5104cd62019-12-10 14:36:43 -0800204 // If PSP was in use at the time of exception, it's possible the CPU
205 // wasn't able to push CPU state. To be safe, this first captures scratch
206 // registers before moving forward.
207 //
208 // Stack flag is bit index 2 (0x4) of exc_return value stored in lr. When
209 // this bit is set, the Process Stack Pointer (PSP) was in use. Otherwise,
210 // the Main Stack Pointer (MSP) was in use. (See ARMv7-M Section B1.5.8
211 // for more details)
212 // The following block of assembly is equivalent to:
213 // if (lr & (1 << 2)) {
Armando Montaneza9ca9992021-01-26 17:06:10 -0800214 // msp -= sizeof(CortexMExceptionRegisters);
215 // CortexMExceptionRegisters* state =
216 // (CortexMExceptionRegisters*) msp;
Armando Montanez5104cd62019-12-10 14:36:43 -0800217 // state->r0 = r0;
218 // state->r1 = r1;
219 // state->r2 = r2;
220 // state->r3 = r3;
221 // state->r12 = r12;
222 // }
223 //
224 " tst lr, #(1 << 2) \n"
225 " itt ne \n"
226 " subne sp, sp, %[base_state_size] \n"
227 " stmne sp, {r0-r3, r12} \n"
228
229 // Reserve stack space for additional registers. Since we're in exception
230 // handler mode, the main stack pointer is currently in use.
231 // r0 will temporarily store the end of captured_cpu_state to simplify
232 // assembly for copying additional registers.
233 " mrs r0, msp \n"
234 " sub sp, sp, %[extra_state_size] \n"
235
236 // Store GPRs to stack.
237 " stmdb r0!, {r4-r11} \n"
238
239 // Load special registers.
240 " mov r1, lr \n"
241 " mrs r2, msp \n"
242 " mrs r3, psp \n"
243 " mrs r4, control \n"
244
245 // Store special registers to stack.
246 " stmdb r0!, {r1-r4} \n"
247
248 // Store a pointer to the beginning of special registers in r4 so they can
249 // be restored later.
250 " mov r4, r0 \n"
251
252 // Restore captured_cpu_state pointer to r0. This makes adding more
253 // memory mapped registers easier in the future since they're skipped in
254 // this assembly.
255 " mrs r0, msp \n"
256
257 // Call intermediate handler that packages data.
258 " ldr r3, =pw_PackageAndHandleCpuException \n"
259 " blx r3 \n"
260
261 // Restore state and exit exception handler.
262 // Pointer to saved CPU state was stored in r4.
263 " mov r0, r4 \n"
264
265 // Restore special registers.
266 " ldm r0!, {r1-r4} \n"
267 " mov lr, r1 \n"
268 " msr control, r4 \n"
269
270 // Restore GPRs.
271 " ldm r0, {r4-r11} \n"
272
273 // Restore stack pointers.
274 " msr msp, r2 \n"
275 " msr psp, r3 \n"
276
277 // Exit exception.
278 " bx lr \n"
Armando Montanez5104cd62019-12-10 14:36:43 -0800279 : /*output=*/
Armando Montaneza9ca9992021-01-26 17:06:10 -0800280 : /*input=*/[base_state_size]"i"(sizeof(CortexMExceptionRegisters)),
281 [extra_state_size]"i"(sizeof(CortexMExtraRegisters))
Armando Montanez5104cd62019-12-10 14:36:43 -0800282 // clang-format on
283 );
284}
285
286} // extern "C"
287} // namespace pw::cpu_exception