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