1 //*****************************************************************************
2 //
3 //! @file am_util_faultisr.c
4 //!
5 //! @brief An extended hard-fault handler.
6 //!
7 //! This module is portable to all Ambiq Apollo products with minimal HAL or BSP
8 //! dependencies (SWO output). It collects the fault information into the sHalFaultData
9 //! structure, which it then prints to stdout (typically SWO).
10 //!
11 //! By default this handler, when included in the build, overrides the weak binding of
12 //! the default hardfault handler. It allocates 512 bytes of global variable space for
13 //! a local stack which guarantees diagnostic output under all hardfault conditions. If
14 //! the local stack is not wanted/needed, define AM_HF_NO_LOCAL_STACK below. If
15 //! the local stack is disabled, and the SP was invalid at the time of the hardfault,
16 //! a second hardfault can occur before any diagnostic data is collected or output.
17 //!
18 //! This handler outputs information about the state of the processor at the time
19 //! the hardfault occurred to stdout (typically SWO). If output is not desired remove
20 //! the #define AM_UTIL_FAULTISR_PRINT below. When prints are disabled, the fault
21 //! information is available in the local sHalFaultData structure.
22 //!
23 //! The handler does not return. After outputting the diagnostic information, it
24 //! spins forever, it does not recover or try and return to the program that caused
25 //! the hardfault.
26 //!
27 //! Upon entry (caused by a hardfault), it switches to a local stack in case
28 //! the cause of the hardfault was a stack related issue. The stack is sized
29 //! large enough to provide for the local variables and the stack space needed
30 //! for the ouput functions calls being used.
31 //!
32 //! It is compiler/platform independent enabling it to be used with GCC, Keil,
33 //! IAR and easily ported to other tools chains
34 //!
35 //! @addtogroup faultisr FaultISR - Extended Hard Fault ISR
36 //! @ingroup utils
37 //! @{
38 //
39 //*****************************************************************************
40
41 //*****************************************************************************
42 //
43 // Copyright (c) 2024, Ambiq Micro, Inc.
44 // All rights reserved.
45 //
46 // Redistribution and use in source and binary forms, with or without
47 // modification, are permitted provided that the following conditions are met:
48 //
49 // 1. Redistributions of source code must retain the above copyright notice,
50 // this list of conditions and the following disclaimer.
51 //
52 // 2. Redistributions in binary form must reproduce the above copyright
53 // notice, this list of conditions and the following disclaimer in the
54 // documentation and/or other materials provided with the distribution.
55 //
56 // 3. Neither the name of the copyright holder nor the names of its
57 // contributors may be used to endorse or promote products derived from this
58 // software without specific prior written permission.
59 //
60 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
61 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
62 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
63 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
64 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
65 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
66 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
67 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
68 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
69 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
70 // POSSIBILITY OF SUCH DAMAGE.
71 //
72 // This is part of revision stable-c1f95ddf60 of the AmbiqSuite Development Package.
73 //
74 //*****************************************************************************
75
76 #include <stdint.h>
77 #include "am_mcu_apollo.h"
78
79 //*****************************************************************************
80 //
81 // Macros
82 //
83 //*****************************************************************************
84
85 //#define AM_HF_NO_LOCAL_STACK // when defined use existing MSP (no local stack)
86 //#define AM_UTIL_FAULTISR_PRINT // when defined print output to stdout (SWO)
87
88 //
89 // Macros used by am_util_faultisr_collect_data().
90 //
91 #define AM_REG_SYSCTRL_CFSR_O 0xE000ED28
92 #define AM_REG_SYSCTRL_BFAR_O 0xE000ED38
93 #define AM_REGVAL(x) (*((volatile uint32_t *)(x)))
94
95 //
96 // Macros for valid stack ranges.
97 //
98 #define AM_NUM_STACK_RANGES 1
99
100 #if defined(AM_PART_APOLLO5A) || defined(AM_PART_APOLLO5B)
101 #undef AM_NUM_STACK_RANGES
102 #define AM_NUM_STACK_RANGES 2
103 #define AM_SP_LOW ITCM_BASEADDR
104 #define AM_SP_HIGH (ITCM_BASEADDR + ITCM_MAX_SIZE)
105 #define AM_SP_LOW2 DTCM_BASEADDR
106 #define AM_SP_HIGH2 (DTCM_BASEADDR + DTCM_MAX_SIZE + SSRAM_MAX_SIZE)
107 #elif defined(AM_PART_APOLLO4_API)
108 #define AM_SP_LOW SRAM_BASEADDR
109 #define AM_SP_HIGH (SRAM_BASEADDR + RAM_TOTAL_SIZE)
110 #elif defined(AM_PART_APOLLO3P)
111 #define AM_SP_LOW SRAM_BASEADDR
112 #define AM_SP_HIGH (SRAM_BASEADDR + ( 768 * 1024 ))
113 #elif defined(AM_PART_APOLLO3)
114 #define AM_SP_LOW SRAM_BASEADDR
115 #define AM_SP_HIGH (SRAM_BASEADDR + ( 384 * 1024 ))
116 #elif defined(AM_PART_APOLLO2)
117 #define AM_SP_LOW SRAM_BASEADDR
118 #define AM_SP_HIGH (SRAM_BASEADDR + ( 256 * 1024 ))
119 #elif defined(AM_PART_APOLLO)
120 #define AM_SP_LOW SRAM_BASEADDR
121 #define AM_SP_HIGH (SRAM_BASEADDR + ( 64 * 1024 ))
122 #endif
123
124 //*****************************************************************************
125 //
126 // Globals
127 //
128 //*****************************************************************************
129
130 // temporary stack (in case the HF was caused by invalid stack)
131 #if !defined(AM_HF_NO_LOCAL_STACK)
132 uint64_t gFaultStack[64]; // needs ~320 bytes (8-byte alignment)
133 #endif
134
135 //*****************************************************************************
136 //
137 // Data structures
138 //
139 //*****************************************************************************
140 //
141 // Define a structure for local storage in am_util_faultisr_collect_data().
142 // Set structure alignment to 1 byte to minimize storage requirements.
143 //
144 #pragma pack(1)
145 typedef struct
146 {
147 //
148 // Stacked registers
149 //
150 volatile uint32_t u32R0;
151 volatile uint32_t u32R1;
152 volatile uint32_t u32R2;
153 volatile uint32_t u32R3;
154 volatile uint32_t u32R12;
155 volatile uint32_t u32LR;
156 volatile uint32_t u32PC;
157 volatile uint32_t u32PSR;
158
159 //
160 // Other data
161 //
162 volatile uint32_t u32FaultAddr;
163 volatile uint32_t u32BFAR;
164 volatile uint32_t u32CFSR;
165 volatile uint8_t u8MMSR;
166 volatile uint8_t u8BFSR;
167 volatile uint16_t u16UFSR;
168
169 } am_fault_t;
170
171 //
172 // Restore the default structure alignment
173 //
174 #pragma pack()
175
176 //*****************************************************************************
177 //
178 // Prototypes
179 //
180 //*****************************************************************************
181 uint32_t am_getStackedReg(uint32_t regnum, uint32_t *u32SP);
182 void am_util_faultisr_collect_data(uint32_t *u32IsrSP);
183 bool am_valid_sp(uint32_t *u32IsrSP);
184
185 //
186 // Prototype for printf, if used.
187 //
188 extern uint32_t am_util_stdio_printf(char *pui8Fmt, ...);
189
190 //*****************************************************************************
191 //
192 // HardFault_Handler() gets control when a hard fault occurs. it pass the previous
193 // active stack pointer (MSP or PSP) in R0 to the function am_util_faultisr_collect_data()
194 // which will never return. If AM_HF_NO_LOCAL_STACK is not defined, it sets the MSP to
195 // the local stack, and for M55 devices sets the MSPLIM register.
196 //
197 //*****************************************************************************
198
199 // for GCC, ARM6 and IAR, all use the same inline assembly syntax
200 #if defined(__GNUC_STDC_INLINE__) || defined(__IAR_SYSTEMS_ICC__) || ((defined(__ARMCC_VERSION)) && (__ARMCC_VERSION > 6000000))
201 #if defined(__IAR_SYSTEMS_ICC__)
202 #pragma diag_suppress = Pe940 // Suppress IAR compiler warning about missing
203 // return statement on a non-void function
204 __stackless uint32_t
205 #else // GCC or ARM6
206 uint32_t __attribute__((naked))
207 #endif
HardFault_Handler(void)208 HardFault_Handler(void)
209 {
210 __asm(" tst lr, #4\n" // Check if we should use MSP or PSP
211 " ite eq\n" // Instrs executed when: eq,ne
212 " mrseq r0, msp\n" // t: bit2=0 indicating MSP stack
213 " mrsne r0, psp\n"); // e: bit2=1 indicating PSP stack
214 #if !defined(AM_HF_NO_LOCAL_STACK)
215 __asm(" ldr r1, =gFaultStack\n"); // get address of the base of the temp_stack
216 #if defined(AM_PART_APOLLO5A) || defined(AM_PART_APOLLO5B)
217 __asm(" MSR msplim, r1\n"); // for Apollo5 (M55) set MSP stack limit register
218 #endif
219 __asm(" add r1, r1, #512\n" // address of the top of the stack.
220 " bic r1, #3\n" // make sure the new stack is 8-byte aligned
221 " mov sp, r1\n"); // move the new stack address to the SP
222 #endif // !defined(AM_HF_NO_LOCAL_STACK)
223 __asm(" b am_util_faultisr_collect_data\n"); // no return - simple branch to get fault info
224 }
225 #endif // for GCC, ARM6 and IAR,
226
227 // for ARM5 compiler
228 #if (defined (__ARMCC_VERSION)) && (__ARMCC_VERSION < 6000000)
229 __asm uint32_t
HardFault_Handler(void)230 HardFault_Handler(void)
231
232 #if !defined(AM_HF_NO_LOCAL_STACK) // Apollo5 does not support ARM5 - can not be used for M55
233 {
234 PRESERVE8
235 import am_util_faultisr_collect_data
236 import gFaultStack
237 tst lr, #4 // Check if we should use MSP or PSP
238 ite eq // Instrs executed when: eq,ne
239 mrseq r0, msp // t: bit2=0 indicating MSP stack
240 mrsne r0, psp // e: bit2=1 indicating PSP stack
241 ldr r1, =gFaultStack // get address of the base of the temp_stack
242 add r1, r1, #512 // address of the top of the stack.
243 bic r1, #3 // make sure the new stack is 8-byte aligned
244 mov sp, r1 // move the new stack address to the SP
245 b am_util_faultisr_collect_data // no return - simple branch to get fault info
246 movs r0, r0 // fill instruction for 4-byte alignment - not executed
247 }
248 #else // no local stack
249 {
250 PRESERVE8
251 import am_util_faultisr_collect_data
252 tst lr, #4 // Check if we should use MSP or PSP
253 ite eq // Instrs executed when: eq,ne
254 mrseq r0, msp // t: bit2=0 indicating MSP stack
255 mrsne r0, psp // e: bit2=1 indicating PSP stack
256 b am_util_faultisr_collect_data // no return - simple branch to get fault info
257 movs r0, r0 // fill instruction for 4-byte alignment - not executed
258 }
259 #endif // !defined(AM_HF_NO_LOCAL_STACK)
260
261 #endif // ARM5 Compiler
262
263 //*****************************************************************************
264 //
265 // am_getStackedReg() will retrieve a specified register value, as it was stacked
266 // by the processor after the fault, from the stack.
267 //
268 // The registers are stacked in the following order:
269 // R0, R1, R2, R3, R12, LR, PC, PSR.
270 //
271 // To get R0 from the stack, call getStackedReg(0), r1 is getStackedReg(1)...
272 //
273 //*****************************************************************************
274 uint32_t
am_getStackedReg(uint32_t regnum,uint32_t * u32SP)275 am_getStackedReg(uint32_t regnum, uint32_t *u32SP)
276 {
277 return (u32SP[regnum]);
278 }
279
280 //*****************************************************************************
281 //
282 // am_util_faultisr_collect_data(uint32_t u32IsrSP);
283 //
284 // This function is intended to be called by HardFault_Handler(), called
285 // when the processor receives a hard fault interrupt. This part of the
286 // handler parses through the various fault codes and saves them into a data
287 // structure so they can be readily examined by the user in the debugger or
288 // output to the stdout (SWO). This function does not return to the caller.
289 //
290 // The input u32IsrSP is expected to be the value of the stack pointer when
291 // HardFault_Handler() was called.
292 //
293 //*****************************************************************************
294 void
am_util_faultisr_collect_data(uint32_t * u32IsrSP)295 am_util_faultisr_collect_data(uint32_t *u32IsrSP)
296 {
297 volatile am_fault_t sFaultData;
298
299 #if defined(AM_PART_APOLLO4_API)
300 am_hal_fault_status_t sHalFaultData = {0};
301 #elif defined(AM_PART_APOLLO3) || defined(AM_PART_APOLLO3P) || defined(AM_PART_APOLLO2) || defined(AM_PART_APOLLO)
302 am_hal_mcuctrl_fault_t sHalFaultData = {0};
303 #endif
304
305 uint32_t u32Mask = 0;
306
307 //
308 // Following is a brief overview of fault information provided by the M4.
309 // More details can be found in the Cortex M4/M55 User Guide.
310 //
311 // CFSR (Configurable Fault Status Reg) contains MMSR, BFSR, and UFSR:
312 // 7:0 MMSR (MemManage)
313 // [0] IACCVIOL Instr fetch from a location that does not
314 // permit execution.
315 // [1] DACCVIOL Data access violation flag. MMAR contains
316 // address of the attempted access.
317 // [2] Reserved
318 // [3] MUNSTKERR MemMange fault on unstacking for a return
319 // from exception.
320 // [4] MSTKERR MemMange fault on stacking for exception
321 // entry.
322 // [5] MLSPERR MemMange fault during FP lazy state
323 // preservation.
324 // [6] Reserved
325 // [7] MMARVALID MemManage Fault Addr Reg (MMFAR) valid flag.
326 // 15:8 BusFault
327 // [0] IBUSERR If set, instruction bus error.
328 // [1] PRECISERR Data bus error. Stacked PC points to instr
329 // that caused the fault.
330 // [2] IMPRECISERR Data bus error, but stacked return addr is not
331 // related to the instr that caused the error and
332 // BFAR is not valid.
333 // [3] UNSTKERR Bus fault on unstacking for a return from
334 // exception.
335 // [4] STKERR Bus fault on stacking for exception entry.
336 // [5] LSPERR Bus fault during FP lazy state preservation.
337 // [6] Reserved
338 // [7] BFARVALID BFAR valid.
339 // 31:16 UFSR (UsageFault)
340 // [0] UNDEFINSTR Undefined instruction.
341 // [1] INVSTATE Invalid state.
342 // [2] INVPC Invalid PC load.
343 // [3] NOCP No coprocessor.
344 // [7:4] Reserved
345 // [8] UNALIGNED Unaligned access.
346 // [9] DIVBYZERO Divide by zero.
347 // [15:10] Reserved
348 //
349
350 //
351 // u32Mask is used for 2 things: 1) in the print loop, 2) as a spot to set
352 // a breakpoint at the end of the routine. If the printing is not used,
353 // we'll get a compiler warning; so to avoid that warning, we'll use it
354 // in a dummy assignment here.
355 //
356 sFaultData.u32CFSR = u32Mask; // Avoid compiler warning
357 sFaultData.u32CFSR = AM_REGVAL(AM_REG_SYSCTRL_CFSR_O);
358 sFaultData.u8MMSR = (sFaultData.u32CFSR >> 0) & 0xff;
359 sFaultData.u8BFSR = (sFaultData.u32CFSR >> 8) & 0xff;
360 sFaultData.u16UFSR = (sFaultData.u32CFSR >> 16) & 0xffff;
361
362 //
363 // The address of the location that caused the fault. e.g. if accessing an
364 // invalid data location caused the fault, that address will appear here.
365 //
366 sFaultData.u32BFAR = AM_REGVAL(AM_REG_SYSCTRL_BFAR_O);
367
368 // make sure that the SP points to a valid address (so that accessing the stack frame doesn't cause another fault).
369 if (am_valid_sp(u32IsrSP))
370 {
371 //
372 // The address of the instruction that caused the fault is the stacked PC
373 // if BFSR bit1 is set.
374 //
375 sFaultData.u32FaultAddr = (sFaultData.u8BFSR & 0x02) ? am_getStackedReg(6, u32IsrSP) : 0xffffffff;
376
377 //
378 // Get the stacked registers.
379 // Note - the address of the instruction that caused the fault is u32PC.
380 //
381 sFaultData.u32R0 = am_getStackedReg(0, u32IsrSP);
382 sFaultData.u32R1 = am_getStackedReg(1, u32IsrSP);
383 sFaultData.u32R2 = am_getStackedReg(2, u32IsrSP);
384 sFaultData.u32R3 = am_getStackedReg(3, u32IsrSP);
385 sFaultData.u32R12 = am_getStackedReg(4, u32IsrSP);
386 sFaultData.u32LR = am_getStackedReg(5, u32IsrSP);
387 sFaultData.u32PC = am_getStackedReg(6, u32IsrSP);
388 sFaultData.u32PSR = am_getStackedReg(7, u32IsrSP);
389 }
390 //
391 // Use the HAL MCUCTRL functions to read the fault data.
392 //
393
394 #if defined(AM_PART_APOLLO4_API)
395 am_hal_fault_status_get(&sHalFaultData);
396 #elif defined(AM_PART_APOLLO3) || defined(AM_PART_APOLLO3P)
397 am_hal_mcuctrl_info_get(AM_HAL_MCUCTRL_INFO_FAULT_STATUS, &sHalFaultData);
398 #elif defined(AM_PART_APOLLO2) || defined(AM_PART_APOLLO)
399 am_hal_mcuctrl_fault_status(&sHalFaultData);
400 #endif
401
402 #ifdef AM_UTIL_FAULTISR_PRINT
403 //
404 // If printf has previously been initialized in the application, we should
405 // be able to print out the fault information.
406 //
407 am_util_stdio_printf("** Hard Fault Occurred:\n\n");
408 if (!am_valid_sp(u32IsrSP))
409 {
410 am_util_stdio_printf(" Invalid SP when Hard Fault occured: 0x%08X (no Stacked data)\n\n");
411 }
412 else
413 {
414 am_util_stdio_printf("Hard Fault stacked data:\n");
415 am_util_stdio_printf(" R0 = 0x%08X\n", sFaultData.u32R0);
416 am_util_stdio_printf(" R1 = 0x%08X\n", sFaultData.u32R1);
417 am_util_stdio_printf(" R2 = 0x%08X\n", sFaultData.u32R2);
418 am_util_stdio_printf(" R3 = 0x%08X\n", sFaultData.u32R3);
419 am_util_stdio_printf(" R12 = 0x%08X\n", sFaultData.u32R12);
420 am_util_stdio_printf(" LR = 0x%08X\n", sFaultData.u32LR);
421 am_util_stdio_printf(" PC = 0x%08X\n", sFaultData.u32PC);
422 am_util_stdio_printf(" PSR = 0x%08X\n\n", sFaultData.u32PSR);
423 }
424 am_util_stdio_printf("Other Hard Fault data:\n");
425 am_util_stdio_printf(" Fault address = 0x%08X\n", sFaultData.u32FaultAddr);
426 am_util_stdio_printf(" BFAR (Bus Fault Addr Reg) = 0x%08X\n", sFaultData.u32BFAR);
427 am_util_stdio_printf(" MMSR (Mem Mgmt Fault Status Reg) = 0x%02X\n", sFaultData.u8MMSR);
428 am_util_stdio_printf(" UFSR (Usage Fault Status Reg) = 0x%04X\n", sFaultData.u16UFSR);
429 am_util_stdio_printf(" BFSR (Bus Fault Status Reg) = 0x%02X\n", sFaultData.u8BFSR);
430 //
431 // Print out any bits set in the BFSR.
432 //
433 u32Mask = 0x80;
434 while (u32Mask)
435 {
436 switch (sFaultData.u8BFSR & u32Mask)
437 {
438 case 0x80:
439 am_util_stdio_printf(" BFSR bit7: BFARVALID\n");
440 break;
441 case 0x40:
442 am_util_stdio_printf(" BFSR bit6: RESERVED\n");
443 break;
444 case 0x20:
445 am_util_stdio_printf(" BFSR bit5: LSPERR\n");
446 break;
447 case 0x10:
448 am_util_stdio_printf(" BFSR bit4: STKERR\n");
449 break;
450 case 0x08:
451 am_util_stdio_printf(" BFSR bit3: UNSTKERR\n");
452 break;
453 case 0x04:
454 am_util_stdio_printf(" BFSR bit2: IMPRECISERR\n");
455 break;
456 case 0x02:
457 am_util_stdio_printf(" BFSR bit1: PRECISEERR\n");
458 break;
459 case 0x01:
460 am_util_stdio_printf(" BFSR bit0: IBUSERR\n");
461 break;
462 default:
463 break;
464 }
465 u32Mask >>= 1;
466 }
467
468 #if !defined(AM_PART_APOLLO5A) && !defined(AM_PART_APOLLO5B) // No CPU register block in Apollo5
469 //
470 // Print out any Apollo* Internal fault information - if any
471 //
472 if (sHalFaultData.bICODE || sHalFaultData.bDCODE || sHalFaultData.bSYS)
473 {
474 am_util_stdio_printf("\nMCU Fault data:\n");
475 }
476 if (sHalFaultData.bICODE)
477 {
478 am_util_stdio_printf(" ICODE Fault Address: 0x%08X\n", sHalFaultData.ui32ICODE);
479 }
480 if (sHalFaultData.bDCODE)
481 {
482 am_util_stdio_printf(" DCODE Fault Address: 0x%08X\n", sHalFaultData.ui32DCODE);
483 }
484 if (sHalFaultData.bSYS)
485 {
486 am_util_stdio_printf(" SYS Fault Address: 0x%08X\n", sHalFaultData.ui32SYS);
487 }
488 #endif // !defined(AM_PART_APOLLO5A) && !defined(AM_PART_APOLLO5B)
489
490 am_util_stdio_printf("\n\nDone with output. Entering infinite loop.\n\n");
491
492 //
493 // Spin in an infinite loop.
494 // We need to spin here inside the function so that we have access to
495 // local data, i.e. sFaultData.
496 //
497 #endif // AM_UTIL_FAULTISR_PRINT
498
499 u32Mask = 0;
500
501 while (1) { }; // spin forever
502 }
503
504 //*****************************************************************************
505 //
506 // am_valid_sp(uint32_t u32IsrSP);
507 //
508 // This function does a range on the SP to make sure it appears to be valid
509 //
510 // The input param u32IsrSP is expected to be the value of the stack pointer in
511 // use when the hardfault occured.
512 //
513 //*****************************************************************************
514 bool
am_valid_sp(uint32_t * u32IsrSP)515 am_valid_sp(uint32_t *u32IsrSP)
516 {
517 // test for valid ranges
518 if ( ((uint32_t) u32IsrSP >= AM_SP_LOW) && ((uint32_t) u32IsrSP < AM_SP_HIGH) )
519 {
520 return true;
521 }
522 #if defined(AM_PART_APOLLO5A) || defined(AM_PART_APOLLO5B)
523 if ( ((uint32_t) u32IsrSP >= AM_SP_LOW2) && ((uint32_t) u32IsrSP < AM_SP_HIGH2) )
524 {
525 return true;
526 }
527 #endif
528 return false; // not in any valid range
529 }
530 //*****************************************************************************
531
532
533 //*****************************************************************************
534 //
535 // End Doxygen group.
536 //! @}
537 //
538 //*****************************************************************************
539