I am using a Cortex-M0 processor with a bare metal implementation (no OS). We have a firmware application where we want to allow a third party to write a C function that will be built separate from the rest of the firmware and loaded into memory (sort of like a DLL), and called by the main firmware if detected.
The issue I have is that I want to run this external function in a protected environment so it will not disrupt the main firmware if it creates a fault exception or takes too long to execute. So what I want to do is, from either the hard fault ISR (for fault exceptions) or the timer tick ISR (for execution time issues), manipulate the stack to kill the external function and return execution to the main firmware. I realize this would be straight forward in an RTOS, but the main firmware is already developed and it would take substantial effort to switch it over at this point.
I looked at using try-catch in c++, but the compiler does not seem to support it. So the other option I see is to write some assembly to save the stack pointer before calling the external function, and from the ISR restore the SP and context, and branch to the return point in the main firmware. Can anyone give any pointers on how best to do this, or is there an easier way to accomplish this?
Here is the implementation I ended up using. I made use of the CMSIS intrinsic functions __get_MSP() and __set_MSP() to access the SP. Before calling the protected function, I saved the current SP. The return address is the first item the function pushes on the stack, so I increment the saved SP by one location so it will be pointing at the return address to be used in a fault recovery situation.
The timer tick ISR tracks how long the protected function has been running, and if it times out or a hard fault interrupt occurs, the fault handler is executed. The first thing that is required is to exit interrupt context (otherwise you will be executing the main code from interrupt context, preventing further interrupts), so I determined which stack location relative to the SP holds the ISR return address, and I overwrite it with the address of the fault recovery function. Note that the Timer Tick ISR is at the lowest priority, so the return address will always be to non interrupt code. If the ISR priority is higher, this may not be the case, but I did not verify it.
On exit from either ISR, the fault recovery function is executed. There are two instructions that it requires:
This restores the SP and PC to their proper state as if the protected function has just exited. However, it does not restore any other registers. If the return point is at the end of a function (and the compiler does not try to inline it), it should be okay. In my case, I needed to set StackPtrSave = 0 as that is how the ISRs know when the protected function is running. I tried using a volatile pointer to force the compiler to reload the variable's address to a register after the recovery, but I could not get it to work. In the end I set an attribute to disable optimization on the function so it would always load the address before writing to StackPtrSave.
As noted in the comments, this does not provide a fully protected environment. The protected function could still use a pointer to corrupt either static variables or the stack. I don't see any way to avoid this since the Cortex-m0 does not have a memory protection unit. In my case, the third party responsible for the protected function do have a vested interested in the product's functionality, so I have given guidelines that their code avoid use of pointers or arrays. I think this provides the highest level of protection that can be offered on this platform.