Runtime for ARM Cortex-M
This Guide Article has been written for Version 2.1 of the DATAFLOW Software. For Previous Releases use the version selection in the navigation bar at the top of this page.
The DATAFLOW Runtime for ARM Cortex-M provides a preemptive scheduler. It has been designed to execute active parts in the same way as a prioritized interrupt controller using a single stack. The Implementation is used for Cortex-M0, M3 and M7.
Synopsis
The ARM Cortex-M architecture is designed primarily for the traditional real-time kernels that use multiple per-thread stacks. Therefore, implementation of the non-blocking, single-stack scheduler of the DATAFLOW Runtime is a bit more involved on Cortex-M than other MCUs and works as follows:
- The ARM Cortex-M processor executes the application code (active parts) in the Privileged Thread mode, which is exactly the mode entered out of reset (default mode). The exceptions (including all interrupts) are always processed in the Privileged Handler mode.
- DATAFLOW Runtime uses only the Main Stack Pointer (DATAFLOW is a single stack runtime). The Process Stack Pointer is not used and is not initialized.
- ARM Cortex-M enters interrupt context without disabling interrupts (without setting the
PRIMASK
bit or theBASEPRI
register). Generally, you should not disable interrupts inside your ISRs. In particular, any runtime functions shall be called with interrupts enabled to avoid nested critical sections. - The DATAFLOW Runtime uses the
PendSV
exception (number 14) and theNMI
exception (number 2) to perform asynchronous preemption and return to the preempted active part, respectively. The startup code will initialize the Interrupt Vector Table with the addresses ofPendSV_Handler()
andNMI_Handler()
exception handlers. - The
PendSV
interrupt is set to the lowest priority (0xFF) by the runtime. - It is strongly recommended to not use the lowest priority (0xFF0 for any other interrupt in your application.
- The first line of code in each Interrupt Service Routine (ISR) must call the
RuntimeInterrupts::applicationIsrEntry
method. The last line must call theRuntimeInterrupts::applicationIsrExit
method.NOTE:
When the DATAFLOW Code Generator is used, this is already the case in the generated*ApIrq.cpp
file. ApplicationIsrExit
will post thePendSV
interrupt. This interrupt is always executed when all other (nested) interrupts have been completely handled (tail chaining).- In ARM Cortex-M the whole prioritization of interrupts, including the
PendSV
exception, is performed entirely by the NVIC. Because thePendSV
has the lowest priority in the system, the NVIC tail-chains to thePendSV
exception only after exiting the last nested interrupt. - The pushing of the 8 registers comprising the ARM Cortex-M interrupt stack frame upon entry to
NMI
exception is wasteful in a single-stack kernel, but is necessary to perform full interrupt return to the preempted context through theNMI
's return.
Preemption
In order to ensure that a high priority active part is not blocked by a long running low priority one, the low priority active part is preempted whenever a message is sent to an active part with higher priority. This can happen in synchronous way (sending a message from application code) and asynchronous way (sending a message from an ISR).
Synchronous Preemption
The "synchronous preemption" occurs when one (low-priority) active part is preempted by another (high-priority) active part. DATAFLOW Runtime handles this case as a regular function call. This function call happens inside the RuntimeCore::sendEvent()
function.
Asynchronous Preemption
The "asynchronous preemption" occurs when an interrupt sends a message to an active part with a higher priority as the current one. In ARM Cortex-M, this preemption is hanlded in the PendSV
exception handler.
- The timeline begins with the DATAFLOW Runtime executing the idle loop.
- At some point an interrupt occurs and the CPU immediately suspends the idle loop, pushes the interrupt stack frame to the Main Stack and starts executing the ISR.
- The ISR performs its work. At the end, applicationIsrExit() must be called, which sets the pending flag for the
PendSV
exception in the NVIC. The priority of thePendSV
exception is configured to be the lowest of all exceptions (0xFF), so the ISR continues executing whilePendSV
exception remains pending. At the ISR return, the ARM Cortex-M CPU performs tail-chaining to the pendingPendSV
exception. - The
PendSV
exception is entered via tail-chaining. - The job of the
PendSV
exception is to run theruntimeSchedule()
method, which calls the execute method for each pending message for each active part in order of descending priority.NOTE:
TheruntimeSchedule()
method must run in thread context, whilePendSV
executes in the exception context. The change of the context is achieved by returning from thePendSV
exception context. A custom stack frame is created with the return address set to theruntimeSchedule()
method. - The
runtimeSchedule()
method enables interrupts and calls the execute method of the Low-priority active part. - Some time later a low-priority interrupt occurs. The Low-priority active part is suspended and the CPU pushes the interrupt stack frame to the Main Stack and starts executing the ISR.
- Before the Low-priority ISR completes, it too gets preempted by a High-priority ISR. The CPU pushes another interrupt stack frame and starts executing the High-priority ISR.
- The High-priority ISR sets the pending flag for the
PendSV
exception by means of theapplicationIsrExit()
method. When the High-priority ISR returns, the NVIC does not tail-chain to thePendSV
exception, because a higher-priority ISR thanPendSV
is still active. - The NVIC performs an exception return to the preempted Low-priority interrupt, which finally completes.
- Upon the exit from the Low-priority ISR, it too sets the pending flag for the
PendSV
exception. ThePendSV
is already pended from the High-priority interrupt, so pending is again is redundant, but it is not an error. - The NVIC performs tail-chaining to the
PendSV
exception. - The
PendSV
exception returns to the DATAFLOW scheduler as previously described. The scheduler detects that the High-priority active part has a pending message and calls its execute method. The High-priority active part runs to completion and returns to the scheduler. - The scheduler does not find any more higher-priority active parts to execute and needs to return to the preempted active part. The only way to restore the interrupted context in ARM Cortex-M is through the interrupt return, but the scheduler is executing outside of the interrupt context (in fact, it is executing in the Privileged Thread mode). The scheduler enters the Handler mode by pending the
NMI
exception.NOTE:
TheNMI
exception is pended while interrupts are still disabled. This is not a problem, becauseNMI
cannot be masked by disabling interrupts, so it runs without any problems. - The only job of the
NMI
exception is to discard its own interrupt stack frame, re-enable interrupts, and return using the interrupt stack frame that has been on the stack from the moment of preemption. - The Low-priority active part, which has been preempted all that time, resumes and finally runs to completion and returns to the scheduler. The scheduler does not find any more active parts to execute and causes the
NMI
exception to return to the preempted active part. - The
NMI
exception discards its own interrupt stack frame and returns using the interrupt stack frame from the preempted thread context
Required Module: DATAFLOW Runtime
This Article has been written based on V2.1.1 of the DATAFLOW software.
Latest update 2023-05-31 by WUM.
Comments
0 comments
Please sign in to leave a comment.