Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 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 | |
Armando Montanez | 356bf97 | 2020-06-04 10:35:55 -0700 | [diff] [blame] | 15 | #include "pw_cpu_exception/entry.h" |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 16 | |
Armando Montanez | 356bf97 | 2020-06-04 10:35:55 -0700 | [diff] [blame] | 17 | #include <cstdint> |
| 18 | #include <cstring> |
| 19 | |
| 20 | #include "pw_cpu_exception/handler.h" |
Armando Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 21 | #include "pw_cpu_exception_cortex_m/cpu_state.h" |
| 22 | #include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h" |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 23 | #include "pw_preprocessor/compiler.h" |
| 24 | |
Armando Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 25 | // TODO(pwbug/311): Deprecated naming. |
| 26 | PW_EXTERN_C PW_NO_PROLOGUE __attribute__((alias("pw_cpu_exception_Entry"))) void |
| 27 | pw_CpuExceptionEntry(void); |
| 28 | |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 29 | namespace pw::cpu_exception { |
| 30 | namespace { |
| 31 | |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 32 | // 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. |
| 40 | constexpr 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 Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 44 | bool PspWasActive(const pw_cpu_exception_State& cpu_state) { |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 45 | 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 Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 50 | bool FpuStateWasPushed(const pw_cpu_exception_State& cpu_state) { |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 51 | 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 Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 58 | void CloneBaseRegistersFromPsp(pw_cpu_exception_State* cpu_state) { |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 59 | // If CPU succeeded in pushing context to PSP, copy it to the MSP. |
Armando Montanez | cddab2d | 2021-01-11 19:11:05 -0800 | [diff] [blame] | 60 | if (!(cpu_state->extended.cfsr & kCfsrStkerrMask) && |
| 61 | !(cpu_state->extended.cfsr & kCfsrMstkerrMask)) { |
Armando Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 62 | // TODO(amontanez): {r0-r3,r12} are captured in pw_cpu_exception_Entry(), |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 63 | // 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 Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 68 | sizeof(CortexMExceptionRegisters)); |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 69 | } 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 Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 84 | void RestoreBaseRegistersToPsp(pw_cpu_exception_State* cpu_state) { |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 85 | // 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 Montanez | cddab2d | 2021-01-11 19:11:05 -0800 | [diff] [blame] | 89 | if (!(cpu_state->extended.cfsr & kCfsrStkerrMask) && |
| 90 | !(cpu_state->extended.cfsr & kCfsrMstkerrMask)) { |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 91 | std::memcpy(reinterpret_cast<void*>(cpu_state->extended.psp), |
| 92 | &cpu_state->base, |
Armando Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 93 | sizeof(CortexMExceptionRegisters)); |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 94 | } |
| 95 | } |
| 96 | |
| 97 | // Determines the size of the CPU-pushed context frame. |
Armando Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 98 | uint32_t CpuContextSize(const pw_cpu_exception_State& cpu_state) { |
| 99 | uint32_t cpu_context_size = sizeof(CortexMExceptionRegisters); |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 100 | if (FpuStateWasPushed(cpu_state)) { |
Armando Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 101 | cpu_context_size += sizeof(CortexMExceptionRegistersFpu); |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 102 | } |
| 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 Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 115 | uint32_t CalculatePspDelta(const pw_cpu_exception_State& cpu_state) { |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 116 | // 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 Montanez | cddab2d | 2021-01-11 19:11:05 -0800 | [diff] [blame] | 119 | if (!PspWasActive(cpu_state) || (cpu_state.extended.cfsr & kCfsrStkerrMask) || |
| 120 | (cpu_state.extended.cfsr & kCfsrMstkerrMask)) { |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 121 | 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 Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 130 | uint32_t CalculateMspDelta(const pw_cpu_exception_State& cpu_state) { |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 131 | 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 Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 136 | return sizeof(CortexMExceptionRegisters) + sizeof(CortexMExtraRegisters); |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 137 | } |
| 138 | |
Armando Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 139 | return CpuContextSize(cpu_state) + sizeof(CortexMExtraRegisters); |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 140 | } |
| 141 | |
| 142 | } // namespace |
| 143 | |
| 144 | extern "C" { |
| 145 | |
| 146 | // Collect remaining CPU state (memory mapped registers), populate memory mapped |
| 147 | // registers, and call application exception handler. |
Armando Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 148 | PW_USED void pw_PackageAndHandleCpuException( |
| 149 | pw_cpu_exception_State* cpu_state) { |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 150 | // Capture memory mapped registers. |
Armando Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 151 | 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 Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 157 | |
| 158 | // CPU may have automatically pushed state to the program stack. If it did, |
Armando Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 159 | // the values can be copied into in the pw_cpu_exception_State struct that is |
Armando Montanez | 356bf97 | 2020-06-04 10:35:55 -0700 | [diff] [blame] | 160 | // passed to HandleCpuException(). The cpu_state passed to the handler is |
| 161 | // ALWAYS stored on the main stack (MSP). |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 162 | 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 Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 172 | pw_cpu_exception_HandleException(cpu_state); |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 173 | |
| 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 Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 201 | void pw_cpu_exception_Entry(void) { |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 202 | asm volatile( |
Armando Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 203 | // clang-format off |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 204 | // 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 Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 214 | // msp -= sizeof(CortexMExceptionRegisters); |
| 215 | // CortexMExceptionRegisters* state = |
| 216 | // (CortexMExceptionRegisters*) msp; |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 217 | // 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 Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 279 | : /*output=*/ |
Armando Montanez | a9ca999 | 2021-01-26 17:06:10 -0800 | [diff] [blame] | 280 | : /*input=*/[base_state_size]"i"(sizeof(CortexMExceptionRegisters)), |
| 281 | [extra_state_size]"i"(sizeof(CortexMExtraRegisters)) |
Armando Montanez | 5104cd6 | 2019-12-10 14:36:43 -0800 | [diff] [blame] | 282 | // clang-format on |
| 283 | ); |
| 284 | } |
| 285 | |
| 286 | } // extern "C" |
| 287 | } // namespace pw::cpu_exception |