Context switching on Cortex-M microcontrollers requires manually saving and restoring register contents when switching between tasks. This involves stacking key registers onto the process stack during a context switch so their values can be preserved for the next time the task executes. Care must be taken to save all necessary registers in the correct order. This article provides a step-by-step guide on how to properly stack registers manually in Cortex-M devices for context switching.
Overview of Context Switching
Context switching allows multiple tasks to share a single Cortex-M processor. A context switch suspends execution of one task and starts running another. In doing so, the processor state for the original task must be saved so it can be restored later when that task resumes. The processor state includes register contents like the program counter, stack pointer, and general purpose registers.
Cortex-M devices have banked register sets which help with context switching. For example, the xPSR, PC, LR, and SP registers are banked for each exception/privilege level. This allows each task to use its own unique SP value without conflicting. However, the general purpose R0-R12 registers are shared and must be manually saved/restored during a context switch.
Process for Stacking Registers
When a context switch occurs, follow these steps to save register state:
- Get stack pointer for outgoing task from its PSP register
- Push remaining general purpose registers R4-R11 onto stack
- Push LR (EXC_RETURN) onto stack
- Push execution context ID value onto stack
- Get stack pointer for incoming task from its PSP register
- Pop execution context ID off incoming task’s stack
- Pop return address off stack into LR
- Pop general purpose registers R4-R11 off stack
This process manually stacks the necessary registers R4-R12, LR, and an execution context ID onto the outgoing task’s stack. The registers are stacked in a certain order to allow for easy popping off the incoming task’s stack. The context ID identifies what task the stacked registers belong to.
Stack Frame Format
The stack frame created by the register stacking follows this format: /* Stack grows down */ SP-> +0 R4 +4 R5 +8 R6 +12 R7 +16 R8 +20 R9 +24 R10 +28 R11 +32 LR +36 Context ID
This shows the order each register is stacked downwards from the stack pointer. When popping the stack for the incoming task, the registers can be restored by reading up the stack.
Saving Special Registers
Beyond R4-R12 and LR, several special registers may also need to be manually stacked, depending on your context switching needs:
- PSR – Program status register flags like interrupts enabled could be stacked
- PRIMASK – Priority mask register for interrupts
- FAULTMASK – Fault exception masking
- BASEPRI – Base priority masking level
- CONTROL – Special CPU control registers
Whether you need to stack these depends on if your tasks require their values preserved. Stacking PSR is common to save interrupt enabled/disabled state.
Caveats and Considerations
Here are some important caveats when manually stacking registers:
- Ensure stack pointers use 8 byte alignment for Cortex-M devices
- Double check you restore registers for incoming task properly
- Beware stack overflows and underflows
- Stack can be corrupted if interrupts occur during switch
- Switch code must be in Privileged Thread mode
- Stacked data could be corrupted if tasks use same stack
Improper stacking or alignment during a context switch can lead to hard faults or stack corruption errors. The switch code itself must also be immune to interrupts which could corrupt the stack frame being created.
Typical Context Switching Workflow
A typical workflow for Cortex-M context switching looks like:
- Save context of outgoing task
- Update OS task control data structures
- Restore context of incoming task
- Incoming task resumes execution
The critical parts are the calls to save and restore the register contexts. This is where the manual stacking of registers occurs according to the procedure described earlier.
Context Switching Example
Here is an example code snippet that performs a context switch between two tasks using manual register stacking: // curr_task points to OS task control block of current running task // next_task points to OS task control block for new task to switch to void ContextSwitch(struct OSTask *curr_task, struct OSTask *next_task) { // Get stack pointer to use uint32_t curr_sp = curr_task->sp; uint32_t next_sp = next_task->sp; // Push registers R4-R11 to save context __asm volatile( “PUSH {R4-R11} \n\t” “PUSH {LR} \n\t” // EXC_RETURN saved here “PUSH {R0} \n\t” // Task ID ); // Update OS control data structures SelectNextTask(next_task); // Pop registers R4-R11 to restore new context __asm volatile( “POP {R0} \n\t” // Task ID “POP {LR} \n\t” “POP {R4-R11} \n\t” ); // Load stack pointer for next task __set_PSP(next_sp); }
This shows the inline ARM assembly used to actually stack the registers during the switch. The OS kernel handles the rest like selecting the new task and updating scheduling algorithms.
Tools for Debugging Stacked Registers
Debugging manually stacked registers during context switching can be tricky. Here are some tools that can help:
- JTAG debugger single stepping and register views
- Overflow stack canaries to detect corruption
- ARM Keil / GDB debugger stack unwinding
- Debug printf() statements in switch code
- Checksum or hash stack data to check integrity
JTAG debugging provides the lowest level visibility into stacked register contents. Stack canaries are values placed in memory to detect overflow corruption. Debug messages can log info at key points during the switch. And checksums can validate the stack data hasn’t been corrupted during saving/restoring.
Conclusion
Manually stacking registers is a key part of enabling preemptive context switching on Cortex-M devices. The steps outlined here guide you through the process of properly ordering which registers to push and pop during a switch. Following these techniques allows multiple tasks to share the Cortex-M processor efficiently and effectively.