• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // 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 
15 #include "pw_cpu_exception/entry.h"
16 
17 #include <cstdint>
18 #include <cstring>
19 
20 #include "pw_cpu_exception/handler.h"
21 #include "pw_cpu_exception_cortex_m/cpu_state.h"
22 #include "pw_cpu_exception_cortex_m/util.h"
23 #include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h"
24 #include "pw_preprocessor/arch.h"
25 #include "pw_preprocessor/compiler.h"
26 
27 // TODO(pwbug/311): Deprecated naming.
28 PW_EXTERN_C PW_NO_PROLOGUE __attribute__((alias("pw_cpu_exception_Entry")))
29 __attribute__((nothrow)) void
30 pw_CpuExceptionEntry(void);
31 
32 namespace pw::cpu_exception::cortex_m {
33 namespace {
34 
35 // Checks exc_return to determine if FPU state was pushed to the stack in
36 // addition to the base CPU context frame.
FpuStateWasPushed(const pw_cpu_exception_State & cpu_state)37 bool FpuStateWasPushed(const pw_cpu_exception_State& cpu_state) {
38   return !(cpu_state.extended.exc_return & kExcReturnBasicFrameMask);
39 }
40 
41 // If the CPU successfully pushed context on exception, copy it into cpu_state.
42 //
43 // For more information see (See ARMv7-M Section B1.5.11, derived exceptions
44 // on exception entry).
CloneBaseRegistersFromPsp(pw_cpu_exception_State * cpu_state)45 void CloneBaseRegistersFromPsp(pw_cpu_exception_State* cpu_state) {
46   // If CPU succeeded in pushing context to PSP, copy it to the MSP.
47   if (!(cpu_state->extended.cfsr & kCfsrStkerrMask) &&
48 #if _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
49       !(cpu_state->extended.cfsr & kCfsrStkofMask) &&
50 #endif  // _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
51       !(cpu_state->extended.cfsr & kCfsrMstkerrMask)) {
52     // TODO(amontanez): {r0-r3,r12} are captured in pw_cpu_exception_Entry(),
53     //                  so this only really needs to copy pc, lr, and psr. Could
54     //                  (possibly) improve speed, but would add marginally more
55     //                  complexity.
56     std::memcpy(&cpu_state->base,
57                 reinterpret_cast<void*>(cpu_state->extended.psp),
58                 sizeof(ExceptionRegisters));
59   } else {
60     // If CPU context wasn't pushed to stack on exception entry, we can't
61     // recover psr, lr, and pc from exception-time. Make these values clearly
62     // invalid.
63     cpu_state->base.lr = kUndefinedPcLrOrPsrRegValue;
64     cpu_state->base.pc = kUndefinedPcLrOrPsrRegValue;
65     cpu_state->base.psr = kUndefinedPcLrOrPsrRegValue;
66   }
67 }
68 
69 // If the CPU successfully pushed context on exception, restore it from
70 // cpu_state. Otherwise, don't attempt to restore state.
71 //
72 // For more information see (See ARMv7-M Section B1.5.11, derived exceptions
73 // on exception entry).
RestoreBaseRegistersToPsp(pw_cpu_exception_State * cpu_state)74 void RestoreBaseRegistersToPsp(pw_cpu_exception_State* cpu_state) {
75   // If CPU succeeded in pushing context to PSP on exception entry, restore the
76   // contents of cpu_state to the CPU-pushed register frame so the CPU can
77   // continue. Otherwise, don't attempt as we'll likely end up in an escalated
78   // hard fault.
79   if (!(cpu_state->extended.cfsr & kCfsrStkerrMask) &&
80 #if _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
81       !(cpu_state->extended.cfsr & kCfsrStkofMask) &&
82 #endif  // _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
83       !(cpu_state->extended.cfsr & kCfsrMstkerrMask)) {
84     std::memcpy(reinterpret_cast<void*>(cpu_state->extended.psp),
85                 &cpu_state->base,
86                 sizeof(ExceptionRegisters));
87   }
88 }
89 
90 // Determines the size of the CPU-pushed context frame.
CpuContextSize(const pw_cpu_exception_State & cpu_state)91 uint32_t CpuContextSize(const pw_cpu_exception_State& cpu_state) {
92   uint32_t cpu_context_size = sizeof(ExceptionRegisters);
93   if (FpuStateWasPushed(cpu_state)) {
94     cpu_context_size += sizeof(ExceptionRegistersFpu);
95   }
96   if (cpu_state.base.psr & kPsrExtraStackAlignBit) {
97     // Account for the extra 4-bytes the processor
98     // added to keep the stack pointer 8-byte aligned
99     cpu_context_size += 4;
100   }
101 
102   return cpu_context_size;
103 }
104 
105 // On exception entry, the Program Stack Pointer is patched to reflect the state
106 // at exception-time. On exception return, it is restored to the appropriate
107 // location. This calculates the delta that is used for these patch operations.
CalculatePspDelta(const pw_cpu_exception_State & cpu_state)108 uint32_t CalculatePspDelta(const pw_cpu_exception_State& cpu_state) {
109   // If CPU context was not pushed to program stack (because program stack
110   // wasn't in use, or an error occurred when pushing context), the PSP doesn't
111   // need to be shifted.
112   if (!ProcessStackActive(cpu_state) ||
113       (cpu_state.extended.cfsr & kCfsrStkerrMask) ||
114 #if _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
115       (cpu_state.extended.cfsr & kCfsrStkofMask) ||
116 #endif  // _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
117       (cpu_state.extended.cfsr & kCfsrMstkerrMask)) {
118     return 0;
119   }
120 
121   return CpuContextSize(cpu_state);
122 }
123 
124 // On exception entry, the Main Stack Pointer is patched to reflect the state
125 // at exception-time. On exception return, it is restored to the appropriate
126 // location. This calculates the delta that is used for these patch operations.
CalculateMspDelta(const pw_cpu_exception_State & cpu_state)127 uint32_t CalculateMspDelta(const pw_cpu_exception_State& cpu_state) {
128   if (ProcessStackActive(cpu_state)) {
129     // TODO(amontanez): Since FPU state isn't captured at this time, we ignore
130     //                  it when patching MSP. To add FPU capture support,
131     //                  delete this if block as CpuContextSize() will include
132     //                  FPU context size in the calculation.
133     return sizeof(ExceptionRegisters) + sizeof(ExtraRegisters);
134   }
135 
136   return CpuContextSize(cpu_state) + sizeof(ExtraRegisters);
137 }
138 
139 }  // namespace
140 
141 extern "C" {
142 
143 // Collect remaining CPU state (memory mapped registers), populate memory mapped
144 // registers, and call application exception handler.
pw_PackageAndHandleCpuException(pw_cpu_exception_State * cpu_state)145 PW_USED void pw_PackageAndHandleCpuException(
146     pw_cpu_exception_State* cpu_state) {
147   // Capture memory mapped registers.
148   cpu_state->extended.cfsr = cortex_m_cfsr;
149   cpu_state->extended.mmfar = cortex_m_mmfar;
150   cpu_state->extended.bfar = cortex_m_bfar;
151   cpu_state->extended.icsr = cortex_m_icsr;
152   cpu_state->extended.hfsr = cortex_m_hfsr;
153   cpu_state->extended.shcsr = cortex_m_shcsr;
154 
155   // CPU may have automatically pushed state to the program stack. If it did,
156   // the values can be copied into in the pw_cpu_exception_State struct that is
157   // passed to HandleCpuException(). The cpu_state passed to the handler is
158   // ALWAYS stored on the main stack (MSP).
159   if (ProcessStackActive(*cpu_state)) {
160     CloneBaseRegistersFromPsp(cpu_state);
161     // If PSP wasn't active, this delta is 0.
162     cpu_state->extended.psp += CalculatePspDelta(*cpu_state);
163   }
164 
165   // Patch captured stack pointers so they reflect the state at exception time.
166   cpu_state->extended.msp += CalculateMspDelta(*cpu_state);
167 
168   // Call application-level exception handler.
169   pw_cpu_exception_HandleException(cpu_state);
170 
171   // Restore program stack pointer so exception return can restore state if
172   // needed.
173   // Note: The default behavior of NOT subtracting a delta from MSP is
174   // intentional. This simplifies the assembly to pop the exception state
175   // off the main stack on exception return (since MSP currently reflects
176   // exception-time state).
177   cpu_state->extended.psp -= CalculatePspDelta(*cpu_state);
178 
179   // If PSP was active and the CPU pushed a context frame, we must copy the
180   // potentially modified state from cpu_state back to the PSP so the CPU can
181   // resume execution with the modified values.
182   if (ProcessStackActive(*cpu_state)) {
183     // In this case, there's no need to touch the MSP as it's at the location
184     // before we entering the exception (effectively popping the state initially
185     // pushed to the main stack).
186     RestoreBaseRegistersToPsp(cpu_state);
187   } else {
188     // Since we're restoring context from MSP, we DO need to adjust MSP to point
189     // to CPU-pushed context frame so it can be properly restored.
190     // No need to adjust PSP since nothing was pushed to program stack.
191     cpu_state->extended.msp -= CpuContextSize(*cpu_state);
192   }
193 }
194 
195 // Captures faulting CPU state on the main stack (MSP), then calls the exception
196 // handlers.
197 // This function should be called immediately after an exception.
pw_cpu_exception_Entry()198 PW_USED PW_NO_PROLOGUE void pw_cpu_exception_Entry() {
199   asm volatile(
200       // clang-format off
201 
202       // Enable unified syntax for Thumb and Thumb2.
203       " .syntax unified                         \n"
204 
205       // If PSP was in use at the time of exception, it's possible the CPU
206       // wasn't able to push CPU state. To be safe, this first captures scratch
207       // registers before moving forward.
208       //
209       // Stack flag is bit index 2 (0x4) of exc_return value stored in lr. When
210       // this bit is set, the Process Stack Pointer (PSP) was in use. Otherwise,
211       // the Main Stack Pointer (MSP) was in use. (See ARMv7-M Section B1.5.8
212       // for more details)
213       // The following block of assembly is equivalent to:
214       //   if (lr & (1 << 2)) {
215       //     msp -= sizeof(ExceptionRegisters);
216       //     ExceptionRegisters* state =
217       //         (ExceptionRegisters*) msp;
218       //     state->r0 = r0;
219       //     state->r1 = r1;
220       //     state->r2 = r2;
221       //     state->r3 = r3;
222       //     state->r12 = r12;
223       //   }
224       //
225       " tst lr, #(1 << 2)                                     \n"
226       " itt ne                                                \n"
227       " subne sp, sp, %[base_state_size]                      \n"
228       " stmne sp, {r0-r3, r12}                                \n"
229 
230       // Reserve stack space for additional registers. Since we're in exception
231       // handler mode, the main stack pointer is currently in use.
232       // r0 will temporarily store the end of captured_cpu_state to simplify
233       // assembly for copying additional registers.
234       " mrs r0, msp                                           \n"
235       " sub sp, sp, %[extra_state_size]                       \n"
236 
237       // Store GPRs to stack.
238       " stmdb r0!, {r4-r11}                                   \n"
239 
240       // Load special registers.
241       " mov r1, lr                                            \n"
242       " mrs r2, msp                                           \n"
243       " mrs r3, psp                                           \n"
244       " mrs r4, control                                       \n"
245 
246 #if _PW_ARCH_ARM_V7M || _PW_ARCH_ARM_V7EM
247       // Store special registers to stack.
248       " stmdb r0!, {r1-r4}                                    \n"
249 
250 #elif _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
251       // Load ARMv8-M specific special registers.
252       " mrs r5, msplim                                        \n"
253       " mrs r6, psplim                                        \n"
254 
255       // Store special registers to stack.
256       " stmdb r0!, {r1-r6}                                    \n"
257 #else
258 #error "Support required for your Cortex-M Arch"
259 #endif  // defined(PW_CPU_EXCEPTION_CORTEX_M_ARMV7M)
260 
261       // Store a pointer to the beginning of special registers in r4 so they can
262       // be restored later.
263       " mov r4, r0                                            \n"
264 
265       // Restore captured_cpu_state pointer to r0. This makes adding more
266       // memory mapped registers easier in the future since they're skipped in
267       // this assembly.
268       " mrs r0, msp                                           \n"
269 
270       // Call intermediate handler that packages data.
271       " ldr r3, =pw_PackageAndHandleCpuException              \n"
272       " blx r3                                                \n"
273 
274       // Restore state and exit exception handler.
275       // Pointer to saved CPU state was stored in r4.
276       " mov r0, r4                                            \n"
277 
278       // Restore special registers.
279 #if _PW_ARCH_ARM_V7M || _PW_ARCH_ARM_V7EM
280       " ldm r0!, {r1-r4}                                      \n"
281 #elif _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
282       " ldm r0!, {r1-r6}                                      \n"
283       " msr msplim, r5                                        \n"
284       " msr psplim, r6                                        \n"
285 #endif  // defined(PW_CPU_EXCEPTION_CORTEX_M_ARMV7M)
286       " mov lr, r1                                            \n"
287       " msr control, r4                                       \n"
288 
289       // Restore GPRs.
290       " ldm r0, {r4-r11}                                      \n"
291 
292       // Restore stack pointers.
293       " msr msp, r2                                           \n"
294       " msr psp, r3                                           \n"
295 
296       // Exit exception.
297       " bx lr                                                 \n"
298       : /*output=*/
299       : /*input=*/[base_state_size]"i"(sizeof(ExceptionRegisters)),
300                   [extra_state_size]"i"(sizeof(ExtraRegisters))
301       // clang-format on
302   );
303 }
304 
305 }  // extern "C"
306 }  // namespace pw::cpu_exception::cortex_m
307