This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Safe exit from HARD FAULT on CortexM0

Hi All, 

I am developing on a CM0+ with functional safety support. 
The safety manual requires to test some features before activating safety functions; many of these are straightforward while others are "convoluted". 

One of the requirements is access to an unimplemented space ad expect the abort to occur. This of course leads to an (expected) Hard Fault exception. My routine in the hard fault handler is able to detect if it is a testor not, and in case of test just set a flag and then exits from the hard fault handler. 

As expected, the unstacking of the registers leads to the instruction that triggered the fault, entering in a faulty instruction- hardfault handler- faulty instruction loop. 

Now, my idea is to modify the stacked program counter in order to return to the next instruction and going on with the regular program flow. 

Many questions here: 

  1. is this the correct approach? 
  2. best practice is to use PC or LR for this kind of operation?
  3. inspecting the assembly, I can see that the hard fault handler pushes 3 registers (push {r3, r4, r7, lr}) in addition to usual R0-R3, R12, PC and LR. why?
  4. In ArmV6m architecture hard faults are considered fatal. Still is "good" to exit from the hard fault is it was expected and wanted?

Any Hint would be gladly appreciated; 

Thanks and best regards, 

Parents
  • Is there any information that could allow me to retrieve the number of words pushed in hard fault handler in the ARM registers?
    My concern is that the assembly will change with different optimization levels so I can't rely on a "fixed" implementation, I would say. 

Reply
  • Is there any information that could allow me to retrieve the number of words pushed in hard fault handler in the ARM registers?
    My concern is that the assembly will change with different optimization levels so I can't rely on a "fixed" implementation, I would say. 

Children
  • So one way around the stack pushing is to have the hard fault handler in assembly which calls another function to do the processing.  Then the hard fault handler does not push any more registers to stack.  Then in the function called from hard fault you can get the data from stack and do analysis, see code below. 

    Please note that in production code often the best thing to do in hard fault is to reset the processor. That is a hard fault could because of a random bit flip in SRAM (single event upset) which means if you try to continue running you will just dig the hole deeper in most cases. Hence you want to reset processor and get back to work. 

    void prvGetRegistersFromStack( uint32_t *pulFaultStackAddress )
    {
    /* These are volatile to try and prevent the compiler/linker optimising them
    away as the variables never actually get used. If the debugger won't show the
    values of the variables, make them global my moving their declaration outside
    of this function. */
    volatile __attribute__((unused)) uint32_t r0;
    volatile __attribute__((unused)) uint32_t r1;
    volatile __attribute__((unused)) uint32_t r2;
    volatile __attribute__((unused)) uint32_t r3;
    volatile __attribute__((unused)) uint32_t r12;
    volatile __attribute__((unused)) uint32_t lr; /* Link register. */
    volatile __attribute__((unused)) uint32_t pc; /* Program counter. */
    volatile __attribute__((unused)) uint32_t psr;/* Program status register. */
    volatile __attribute__((unused)) uint32_t _CFSR ;
    volatile __attribute__((unused)) uint32_t _HFSR ;
    volatile __attribute__((unused)) uint32_t _DFSR ;
    volatile __attribute__((unused)) uint32_t _AFSR ;
    volatile __attribute__((unused)) uint32_t _BFAR ;
    volatile __attribute__((unused)) uint32_t _MMAR ;

    r0 = pulFaultStackAddress[ 0 ];
    r1 = pulFaultStackAddress[ 1 ];
    r2 = pulFaultStackAddress[ 2 ];
    r3 = pulFaultStackAddress[ 3 ];

    r12 = pulFaultStackAddress[ 4 ];
    lr = pulFaultStackAddress[ 5 ];
    pc = pulFaultStackAddress[ 6 ];
    psr = pulFaultStackAddress[ 7 ];

    // Configurable Fault Status Register
    // Consists of MMSR, BFSR and UFSR
    _CFSR = (*((volatile unsigned long *)(0xE000ED28))) ;

    // Hard Fault Status Register
    _HFSR = (*((volatile unsigned long *)(0xE000ED2C))) ;

    // Debug Fault Status Register
    _DFSR = (*((volatile unsigned long *)(0xE000ED30))) ;

    // Auxiliary Fault Status Register
    _AFSR = (*((volatile unsigned long *)(0xE000ED3C))) ;

    // Read the Fault Address Registers. These may not contain valid values.
    // Check BFARVALID/MMARVALID to see if they are valid values
    // MemManage Fault Address Register
    _MMAR = (*((volatile unsigned long *)(0xE000ED34))) ;
    // Bus Fault Address Register
    _BFAR = (*((volatile unsigned long *)(0xE000ED38))) ;

    /* When the following line is hit, the variables contain the register values. */
    #if defined DEBUG
    __BKPT(3);
    #endif
    for( ;; );  //This is where you would call the NVIC reset in production after logging error if needed
    }


    /* The prototype shows it is a naked function - in effect this is just an
    assembly function. */
    void HardFault_DUMMY( void ) __attribute__( ( naked ) );

    /* The fault handler implementation calls a function called
    prvGetRegistersFromStack(). */
    void HardFault_DUMMY(void)
    {
    __asm volatile
    (
    " tst lr, #4 \n"
    " ite eq \n"
    " mrseq r0, msp \n"
    " mrsne r0, psp \n"
    " ldr r1, [r0, #24] \n"
    " ldr r2, handler2_address_const \n"
    " bx r2 \n"
    " handler2_address_const: .word prvGetRegistersFromStack \n"
    );
    }

  • During hardfault (actually all exceptions) entry, the return address is pushed into SP+18. This is done by hardware and I don't think a proper code will change it

  • Hello Trampas, 

    thanks for your reply. Your approach seems suitable to my use case, using naked functions. 
    Nevertheless, based onthe suggestions of the community, I decided for a slightly different approach: since the hard fault is generated on purpose (I KNOW when and why it happens) I provide the stack pointer value just before launching the fault instruction. 
    Then in the hard fault handler I retrieve this information, subtract the value from the current stack pointer and I am then able to understand the offset value toa dd to the current stack pointer to retrieve the PC: 

    SP + offset +0x18.

  • What happens when/if the processor gets a real Hard Fault, ie not one generated by test code?