This article is a natural extension on the topics we covered in Function Calls on ARM Cortex-M Microprocessors. Due to the hardware specifics of the Cortex-M architecture, there is actually a lot of common in how function calls and exceptions are handled. The good thing for us as developers is that all exception handlers can be written as regular C functions. How that happens and what actions are performed by the software and by the hardware is the focus of this article.
Exceptions and Context Switching
Exceptions are events that disrupt the normal execution flow of the program. When an exception occurs the processor handles it by executing a dedicated piece of code called an exception handler (a.k.a interrupt service routine ISR). Once this exception handler code is executed, the CPU must return to the regular program that was being executed at the time the exception occurred. This switching between the regular program code and the exception handling code requires the implementation of context switching.
Context switching is the process of saving the state of a processor or a task (e.g in real-time operating systems) with the intention for it to be restored at a later point in time. The context switching is between the main program and the exception handler code that has to be executed when an exception occurs ( if nested exceptions are allowed we can have further context switching between exception handlers). The state of the processor includes all relevant resources used by the interrupted program, that can be used also by the exception handler routine.
ARM Cortex-M Specifics
The context switching when an exception occurs in the ARM Cortex-M CPU is handled using a hardware and software component. The hardware is the CPU and the NVIC that automatically saves some resources when an exception occurs. The software handling is done by the compiler, complying with the requirement for callee-save registers described in Procedure Call Standard for the ARM® Architecture. that we covered in our article Function Calls on ARM Cortex-M microprocessors.
For understanding the exception handling sequence, some specifics of the ARM Cortex-M microprocessors should be mentioned.
The processor has the following operating modes:
- Thread mode – Used to execute application code. It is the processor’s default operating mode after reset.
- Handler mode – Used to handle exceptions. After the exceptions are finished, the processor returns to Thread mode.
ARM Cortex-M architecture has two stacks pointers- Main Stack Pointer (MSP) and Processor Stack Pointer (PSP). At any point in time, only one can be the active stack. The MSP is the default stack pointer and it is the one that is always used in Handler Mode. In Thread mode, the stack pointer can be selected using SPSEL bit from the CONTROL register. The availability of two stacks accommodates the use of “multitasking” real-time operating systems. The usual use case is that the processor stack is used by the application tasks, and the main stack is used by exception handlers and the RTOS kernel.
On exception entry, the NVIC automatically saves a specific set of registers called the exception frame (see Fig.1) onto the current stack ( this is the stack that the processor is using when the exception request is accepted ). The caller-save registers (R0-R3) are part of this frame. For regular C function calling, the compiler generates code to save the caller save registers. By having these registers automatically saved when an exception is entered it is possible to use regular C functions as interrupt service routines (ISR). After saving the exception frame, the hardware then puts a special EXC_RETURN value into the LR register. This value is used for exiting the exception. The processor execution mode is switched to Handler mode (if the current mode was Thread mode). By entering Handler mode, the stack pointer is switched to MSP. Next, the address of the exception handler is retrieved from the vector table and loaded into the PC, thus jumping to the handler code is performed. The compilator will push the callee-save registers into the stack as it does with all regular C functions, upon their entry.
The sequence for returning from an exception is initiated when the PC is loaded with a special value (EXC_RETURN) using either a POP instruction with loading the PC or BX instruction with any register. Example:
BX LR; //The program counter is loaded with the value stored in LR register. This should be the EXC_RETURN value.
The EXC_RETURN is usually stored in the LR register as part of the exception entry sequence. The EXC_RETURN values are special values (not a regular memory addresses) that are recognized by the CPU. They indicate that the exception is complete and the exception exit sequence should be started. Based on the EXC_RETURN value, the CPU retrieves information on which stack pointer is used (MSP or PSP), the type of saved exception frame and the current execution mode (thread or handler). All registers (part of the exception frame) that were saved by being pushed onto the stack upon exception entry are now restored automatically by the hardware. The compilator generated code will take care of restoring the callee-save registers. The exception is exited and the execution of the application program continues where it was interrupted.