An ISB (Instruction Synchronization Barrier) is needed after a WFI (Wait For Interrupt) instruction in Cortex-M based microcontrollers running FreeRTOS to ensure proper synchronization between the FreeRTOS scheduler and interrupt handling. The key reason is that the Cortex-M processor may speculatively fetch instructions ahead of the current instruction stream, which can lead to race conditions if interrupts occur at certain points.
More specifically, here is what happens without an ISB after WFI:
- The WFI instruction is executed by the processor. This halts execution until an interrupt occurs.
- However, the processor may have speculatively fetched instructions ahead of WFI already. These are sitting in the pipeline waiting to execute.
- An interrupt occurs and the interrupt handler starts executing.
- The interrupt handler may trigger a context switch in the FreeRTOS scheduler.
- The scheduler switches to another thread and starts executing it.
- Meanwhile, the speculatively fetched instructions before WFI may still be making their way through the pipeline and get executed!
This can lead to logical problems, race conditions, and program crashes because instructions not belonging to the current thread may execute unexpectedly.
Putting an ISB instruction after the WFI acts as a pipeline flush/drain and ensures any speculatively fetched instructions are discarded. This prevents errant instruction execution and synchronizes the pipeline state correctly after the interrupt and context switch.
Let’s go through this in more detail…
1. Speculative Instruction Fetching in Cortex-M
Modern processors like Cortex-M employ speculative instruction fetching to improve performance. This means the processor will try to fetch instructions ahead of the current execution stream and buffer them in the pipeline.
Speculative fetching helps keep the pipeline primed and ready so execution can proceed with minimal stalls when branches are encountered. However, it can also lead to logically incorrect behavior in certain scenarios.
2. The WFI Instruction
WFI stands for “Wait For Interrupt”. When a Cortex-M processor executes WFI, it will halt execution and essentially go into a low power state until an interrupt occurs.
This is commonly used in embedded programs for power savings. The processor can stay halted in WFI until an important interrupt wakes it up to do some work.
3. Race Condition Without ISB
Here is what happens without ISB after WFI:
- WFI is executed, which halts the processor until an interrupt.
- But speculative fetching has sent future instructions (after WFI) into the pipeline already.
- An interrupt occurs and the interrupt handler starts executing.
- The handler triggers a FreeRTOS context switch.
- The switched-to thread starts executing.
- The speculatively fetched instructions (past WFI) may still execute out-of-order!
This out-of-order and interleaved execution with the new thread can create serious logical bugs and race conditions.
4. The ISB Instruction
ISB stands for “Instruction Synchronization Barrier”. It acts as a pipeline flush/drain instruction.
When ISB is executed, it will Ensure the pipeline is cleared of any speculatively fetched instructions ahead of the ISB. It effectively synchronizes the instruction stream before and after itself.
5. ISB Prevents Errant Execution
By placing an ISB immediately after the WFI, the pipeline is flushed of any speculatively fetched instructions after WFI.
Now the instruction stream is synchronized properly after the interrupt and context switch take place.
Any instructions from the switched-to thread will pass through the pipeline in order with no possibility of out-of-order execution.
This prevents errant speculative instruction execution and race conditions due to the context switch.
6. Common Cortex-M Coding Pattern
The WFI + ISB pattern is commonly seen in Cortex-M firmware using FreeRTOS or other RTOSes: WFI ;halt processor until interrupt ISB ;synchronize pipeline ;continue execution
This ensures clean synchronization and transition from the WFI state to resumed execution after an interrupt and context switch.
7. Other Solutions?
A couple other solutions exist to address this problem:
- Disable speculative instruction fetching altogether. This hurts performance.
- Use DSB instead of ISB. DSB stalls longer though.
- Set SPECIAL_INSTRUCTION_SYNC enable bit. This has overhead.
- Perform WFI in privileged mode. Limiting if using unprivileged threads.
In most cases, the WFI-ISB pattern provides the best tradeoff of safety, performance and flexibility on Cortex-M.
8. Real-World Implications
Forgetting the ISB after WFI can result in gnarly bugs that are hard to root cause. Things like corrupted data, crashes, and general undefined behavior can occur.
Adding the ISB properly synchronizes the pipeline and avoids these issues. It ensures deterministic implementation-defined behavior across Cortex-M variants.
This is crucial for safety-critical applications where reliability and determinism are paramount.
So in summary, the ISB acts as an instruction fence preventing errant speculative execution after WFI during context switches in FreeRTOS environments on Cortex-M processors.
9. Checklist for Using WFI+ISB
- Place WFI where you want to halt for interrupts
- Follow WFI with ISB always
- Use this pattern after vTaskDelayUntil() or similar APIs
- Double check ISBs are present in production code
- Enable CONFIG_ENABLE_WFI_IDLE to use in FreeRTOS idle task
- Consider using CMSIS WFI() and ISB() functions
Properly using WFI-ISB will ensure robust, crash-free embedded applications on Cortex-M!