Context switching between threads or tasks on Cortex-M processors involves saving the context of one thread or task, then loading the context of another. A key part of the context is the EXC_RETURN value, which controls the processor state when returning from an exception. Care must be taken when loading the EXC_RETURN value during a context switch to ensure the target thread’s context is restored correctly.
EXC_RETURN Value Format
The EXC_RETURN value is a 32-bit value with the following bit fields:
- Bits [31:5] – Reserved, must be zero
- Bit [4] – Stack frame type
- Bits [3:2] – Execution state on return (Handler or Thread mode)
- Bits [1:0] – Processor mode on return
The stack frame type indicates whether the stack frame is for an exception handler or a stacked PC on exception entry. The execution state determines whether the processor returns to Handler mode or Thread mode. The processor mode sets the CPSR mode bits to the specified mode.
Saving EXC_RETURN
When an exception occurs, the processor automatically saves an EXC_RETURN value to the stack or to a system register like LR. This value will contain the correct execution state, processor mode, and stack frame type to return to the pre-exception context.
To save the EXC_RETURN value on exception entry for a context switch, the exception handler needs to read the LR or stack value and save it with the rest of the thread’s context. For example: /* Save EXC_RETURN value */ EXC_RETURN = __current_lr();
Loading EXC_RETURN for Thread Mode
When loading an EXC_RETURN value for a thread context, bits [3:2] must be programmed to 0b01 to return to Thread mode. Bits [1:0] should load the thread’s CPSR mode, usually 0b1001 for SYSTEM mode or 0b1011 for UNPRIVILEGED mode.
For example, to load a saved EXC_RETURN value and transition to unprivileged Thread mode: /* Load saved EXC_RETURN value for thread */ EXC_RETURN = (saved_EXC_RETURN & 0xFFFFFFF0) | 0x0000000B;
Loading EXC_RETURN for Handler Mode
To return to Handler mode, bits [3:2] of EXC_RETURN should be programmed to 0b00. The mode bits will depend on whether the handler uses Privileged or Unprivileged mode. /* Load saved EXC_RETURN to return to Privileged Handler mode */ EXC_RETURN = (saved_EXC_RETURN & 0xFFFFFFF0);
Saving Stacked Registers
The stack frame type bit indicates whether stacked registers need to be stored/restored. If set, the stacked registers (R0-R3, R12, LR, PC, xPSR) must be stored and restored by the context switch code.
For example: /* Check for stacked frame */ if (EXC_RETURN[4] == 1) { /* Save stacked registers */ stacked_regs[0] = R0; … stacked_regs[8] = xPSR; }
Restoring Stacked Registers
To restore stacked registers: /* Check for stacked frame */ if (EXC_RETURN[4] == 1) { /* Restore stacked registers */ R0 = stacked_regs[0]; … xPSR = stacked_regs[8]; }
Saving Process Stack Pointer
The process stack pointer (PSP) register holds the current stack pointer for Thread mode. This must be saved and restored during context switches. /* Save PSP */ PSP_CONTEXT = PSP;
Restoring Process Stack Pointer
To restore PSP for Thread mode: /* Load saved PSP */ PSP = PSP_CONTEXT;
References
Here are some references for additional information on EXC_RETURN and Cortex-M context switching:
- ARM Cortex-M Programming Guide
- Cortex-M4 Devices Generic User Guide
- Context Switching on ARM Cortex-M3/M4
Loading the correct EXC_RETURN value and associated registers is key to properly saving and restoring context during multithreading on Cortex-M processors. Carefully managing the stack frame type, execution state, processor mode, stacked registers, and stack pointer will help ensure robust context switching.