mirror of
https://github.com/QuantumLeaps/qpc.git
synced 2025-01-14 06:43:19 +08:00
1591 lines
94 KiB
Plaintext
1591 lines
94 KiB
Plaintext
/*##########################################################################*/
|
||
/*! @page arm-cm ARM Cortex-M
|
||
|
||
@htmlonly
|
||
<script src="preview.js" type="text/javascript"></script>
|
||
@endhtmlonly
|
||
|
||
@tableofcontents
|
||
|
||
<p>This section describes the QP™ port to the ARM Cortex-M processor family (Cortex M0/M0+/M3/M4/M7). Three main implementation options are covered: the @subpage arm-cm_qv "cooperative, priority-based QV kernel", the @subpage arm-cm_qk "preemptive, run-to-completion QK kernel", and the @subpage arm-cm_qxk "preemptive, dual-mode blocking QXK kernel". Additionally, the use of the VFP (floating point coprocessor) in the M4F/M7 processors is explained as well. This document assumes QP version 5.9.x or higher.
|
||
</p>
|
||
|
||
@note
|
||
To focus the discussion, this section references the **GNU-ARM toolset**, the <a class="preview board" href="bd_EK-TM4C123GXL.jpg" title="EK-TM4C123GXL">EK-TM4C123GXL</a> (ARM Cortex-M4F) and the Eclipse-based IDE (CCS from Texas Instruments). However, the general implementation strategy applies equally to all toolsets for ARM Cortex-M, such as **ARM-KEIL**, **IAR EWARM**, **GNU-ARM** and **TI-ARM**, which are all supported as well. The QP code downloads contain also examples for other boards, such as STM32 Nucleo, NXP mbed-1768, SilLabs Gecko and others.
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_files Directories and Files
|
||
The QP ports to ARM Cortex-M are available in the standard QP distribution. Specifically, the ARM Cortex-M ports are placed in the following directories:
|
||
|
||
<ul class="tag">
|
||
<li><span class="img folder">/pors/arm-cm/</span>
|
||
</li>
|
||
<ul class="tag">
|
||
<li><span class="img folder">qv/</span> — QV ports
|
||
</li>
|
||
<ul class="tag">
|
||
<li><span class="img folder">arm/</span> — ARM-KEIL toolset
|
||
</li>
|
||
<li><span class="img folder">gnu/</span> — GNU-ARM toolset
|
||
<ul class="tag">
|
||
<li><span class="img file_h">qep_port.h</span> — QEP port header
|
||
</li>
|
||
<li><span class="img file_h">qf_port.h</span> — QF port header
|
||
</li>
|
||
<li><span class="img file_h">qv_port.h</span> — QV port header
|
||
</li>
|
||
<li><span class="img file_c">qv_port.c</span> — QV port implementation
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li><span class="img folder">iar/</span> — IAR-EWARM toolset
|
||
</li>
|
||
<li><span class="img folder">ti/</span> — TI-ARM toolset
|
||
</li>
|
||
</ul>
|
||
<li><span class="img folder">qk/</span> — QK ports
|
||
</li>
|
||
<ul class="tag">
|
||
<li><span class="img folder">arm/</span> — ARM-KEIL toolset
|
||
</li>
|
||
<li><span class="img folder">gnu/</span> — GNU-ARM toolset
|
||
</li>
|
||
<li><span class="img folder">iar/</span> — IAR-EWARM toolset
|
||
</li>
|
||
<li><span class="img folder">ti/</span> — TI-ARM toolset
|
||
</li>
|
||
</ul>
|
||
<li><span class="img folder">qxk/</span> — QXK ports"
|
||
</li>
|
||
<ul class="tag">
|
||
<li><span class="img folder">arm/</span> — ARM-KEIL toolset
|
||
</li>
|
||
<li><span class="img folder">gnu/</span> — GNU-ARM toolset
|
||
</li>
|
||
<li><span class="img folder">iar/</span> — IAR-EWARM toolset
|
||
</li>
|
||
<li><span class="img folder">ti/</span> — TI-ARM toolset
|
||
</li>
|
||
</ul>
|
||
<li><span class="img folder">qutest/</span> — <a href="https://state-machine.com/qtools/qutest.html" target="_blank" class="extern">QUTest Unit Testing</a> port to Cortex-M"
|
||
</li>
|
||
</ul>
|
||
</ul>
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_int Interrupts in the QP Ports to ARM Cortex-M
|
||
The QP framework, like any real-time kernel, needs to disable interrupts in order to access critical sections of code and re-enable interrupts when done. This section describes the general policy that in the ARM Cortex-M ports of all built-in real time kernels in QP, such as QV, QK, and QXK.
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_kernel-aware "Kernel-Aware" and "Kernel-Unaware" Interrupts
|
||
The QP ports to ARM Cortex-M3/M4/M7 **never completely disables interrupts**, even inside the critical sections. On Cortex-M3/M4/M7 (ARMv7-M architectures), the QP port disables interrupts **selectively** using the BASEPRI register. This policy divides interrupts into "kernel-unaware" interrupts, which are never disabled, and “kernel-aware” interrupts, which are disabled in the QP critical sections.
|
||
|
||
@note
|
||
The BASEPRI register is not implemented in the ARMv6-M architecture (Cortex-M0/M0+), so Cortex-M0/M0+ need to use the PRIMASK register to disable interrupts globally. In other words, in Cortex-M0/M0+ ports, all interrupts are "kernel-aware".
|
||
|
||
|
||
@attention
|
||
Only "kernel-aware" interrupts are allowed to call QP services. "Kernel-unaware" interrupts are not allowed to call any QP services and they can communicate with QP only by triggering a "kernel-aware" interrupt (which can post or publish events).
|
||
|
||
|
||
As illustrated in the figures below, the number of interrupt priority bits actually available is implementation dependent, meaning that the various ARM Cortex-M silicon vendors can provide different number of priority bits, varying from just 3 bits (which is the minimum for ARMv7-M architecture) up to 8 bits. For example, the TI Tiva-C microcontrollers implement only 3 priority bits (see figure above).
|
||
|
||
@image html arm-cm_int3bit.png "Kernel-aware and Kernel-unaware interrupts with 3 priority bits
|
||
|
||
On the other hand, the STM32 MCUs implement 4 priority bits (see figure below). The CMSIS standard provides the macro <strong>__NVIC_PRIO_BITS</strong>, which specifies the number of NVIC priority bits defined in a given ARM Cortex-M implementation.
|
||
|
||
@image html arm-cm_int4bit.png "Kernel-aware and Kernel-unaware interrupts with 4 priority bits
|
||
|
||
Another important fact to note is that the ARM Cortex-M core stores the interrupt priority values in the most significant bits of its eight bit interrupt priority registers inside the NVIC (Nested Vectored Interrupt Controller). For example, if an implementation of a ARM Cortex-M microcontroller only implements three priority bits, then these three bits are shifted up to be bits five, six and seven respectively. The unimplemented bits can be written as zero or one and always read as zero.
|
||
|
||
And finally, the NVIC uses an inverted priority numbering scheme for interrupts, in which priority zero (0) is the highest possible priority (highest urgency) and larger priority numbers denote actually lower-priority interrupts. So for example, interrupt of priority 2 can preempt an interrupt with priority 3, but interrupt of priority 3 cannot preempt interrupt of priority 3. The default value of priority of all interrupts out of reset is zero (0).
|
||
|
||
|
||
@note
|
||
Starting with QP 5.9.x, the QF_init() call sets interrupt priority of all IRQs to the "kernel aware" value <strong>QF_BASEPRI</strong>. Still, it is highly recommended to set the priority of all interrupts used by an application **explicitly**.
|
||
|
||
|
||
The CMSIS provides the function @c NVIC_SetPriority() which you should use to set priority of every interrupt.
|
||
|
||
@note
|
||
The priority scheme passed to @c NVIC_SetPriority() is different again than the values stored in the NVIC registers, as shown in the figures above as "CMSIS priorities"
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_int-assign Assigning Interrupt Priorities
|
||
The @ref exa_arm-cm "example projects" included in the QP distribution the recommended way of assigning interrupt priorities in your applications. The initialization consist of two steps: (1) you enumerate the "kernel-unaware" and "kernel-aware" interrupt priorities, and (2) you assign the priorities by calling the @c NVIC_SetPriority() CMSIS function. The following snippet of code illustrates these steps with the explanation section following immediately after the code.
|
||
|
||
@anchor arm-cm_int-assign-code
|
||
<b>Listing: Assigning the interrupt priorities (see file bsp.c in the example projects)</b>
|
||
@code{c}
|
||
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CAUTION !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||
* Assign a priority to EVERY ISR explicitly by calling NVIC_SetPriority().
|
||
* DO NOT LEAVE THE ISR PRIORITIES AT THE DEFAULT VALUE!
|
||
*/
|
||
[1] enum KernelUnawareISRs { /* see NOTE0 */
|
||
/* ... */
|
||
[2] MAX_KERNEL_UNAWARE_CMSIS_PRI /* keep always last */
|
||
};
|
||
/* "kernel-unaware" interrupts can't overlap "kernel-aware" interrupts */
|
||
[3] Q_ASSERT_COMPILE(MAX_KERNEL_UNAWARE_CMSIS_PRI <= QF_AWARE_ISR_CMSIS_PRI);
|
||
|
||
[4] enum KernelAwareISRs {
|
||
[5] GPIOPORTA_PRI = QF_AWARE_ISR_CMSIS_PRI, /* see NOTE00 */
|
||
SYSTICK_PRIO,
|
||
/* ... */
|
||
[6] MAX_KERNEL_AWARE_CMSIS_PRI /* keep always last */
|
||
};
|
||
/* "kernel-aware" interrupts should not overlap the PendSV priority */
|
||
[7] Q_ASSERT_COMPILE(MAX_KERNEL_AWARE_CMSIS_PRI <= (0xFF>>(8-__NVIC_PRIO_BITS)));
|
||
|
||
~ ~ ~
|
||
|
||
[8] void QF_onStartup(void) {
|
||
/* set up the SysTick timer to fire at BSP_TICKS_PER_SEC rate */
|
||
SysTick_Config(ROM_SysCtlClockGet() / BSP_TICKS_PER_SEC);
|
||
|
||
/* assing all priority bits for preemption-prio. and none to sub-prio. */
|
||
[9] NVIC_SetPriorityGrouping(0U);
|
||
|
||
/* set priorities of ALL ISRs used in the system, see NOTE00
|
||
*
|
||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!! CAUTION !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||
* Assign a priority to EVERY ISR explicitly by calling NVIC_SetPriority().
|
||
* DO NOT LEAVE THE ISR PRIORITIES AT THE DEFAULT VALUE!
|
||
*/
|
||
[10] NVIC_SetPriority(SysTick_IRQn, SYSTICK_PRIO);
|
||
[11] NVIC_SetPriority(GPIOPortA_IRQn, GPIOPORTA_PRIO);
|
||
~ ~ ~
|
||
/* enable IRQs... */
|
||
[12] NVIC_EnableIRQ(GPIOPortA_IRQn);
|
||
}
|
||
@endcode
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">1</span>
|
||
The enumeration @c KernelUnawareISRs lists the priority numbers for the “kernel-unaware” interrupts. These priorities start with zero (highest possible). The priorities are suitable as the argument for the @c NVC_SetPriority() CMSIS function.
|
||
|
||
> NOTE: The NVIC allows you to assign the same priority level to multiple interrupts, so you can have more ISRs than priority levels running as “kernel-unaware” or “kernel-aware” interrupts.
|
||
|
||
</li>
|
||
<li><span class="tag">2</span>
|
||
The last value in the enumeration MAX_KERNEL_UNAWARE_CMSIS_PRI keeps track of the maximum priority used for a “kernel-unaware” interrupt.
|
||
</li>
|
||
<li><span class="tag">3</span>
|
||
The compile-time assertion ensures that the “kernel-unaware” interrupt priorities do not overlap the “kernel-aware” interrupts, which start at QF_AWARE_ISR_CMSIS_PRI.
|
||
</li>
|
||
<li><span class="tag">4</span>
|
||
The enumeration KernelAwareISRs lists the priority numbers for the “kernel-aware” interrupts.
|
||
</li>
|
||
<li><span class="tag">5</span>
|
||
The “kernel-aware” interrupt priorities start with the QF_AWARE_ISR_CMSIS_PRI offset, which is provided in the qf_port.h header file.
|
||
</li>
|
||
<li><span class="tag">6</span>
|
||
The last value in the enumeration MAX_KERNEL_AWARE_CMSIS_PRI keeps track of the maximum priority used for a “kernel-aware” interrupt.
|
||
</li>
|
||
<li><span class="tag">7</span>
|
||
The compile-time assertion ensures that the “kernel-aware” interrupt priorities do not overlap the lowest priority level reserved for the PendSV exception.
|
||
</li>
|
||
<li><span class="tag">8</span>
|
||
The QF_onStartup() callback function is where you set up the interrupts.
|
||
</li>
|
||
<li><span class="tag">9</span>
|
||
This call to the CMIS function @c NVIC_SetPriorityGrouping() assigns all the priority bits to be preempt priority bits, leaving no priority bits as subpriority bits to preserve the direct relationship between the interrupt priorities and the ISR preemption rules. This is the default configuration out of reset for the ARM Cortex-M3/M4 cores, but it can be changed by some vendor-supplied startup code. To avoid any surprises, the call to NVIC_SetPriorityGrouping(0U) is recommended.
|
||
</li>
|
||
<li><span class="tag">10-11</span>
|
||
The interrupt priories fall all interrupts (“kernel-unaware” and “kernel-aware” alike) are set explicitly by calls to the CMSIS function @c NVIC_SetPriority().
|
||
</li>
|
||
<li><span class="tag">12</span>
|
||
All used IRQ interrupts need to be explicitly enabled by calling the CMSIS function.
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_int-fpu Interrupts and the FPU (Cortex-M4F/M7)
|
||
The QP ports described in this section support also the ARM Cortex-M4F/M7. Compared to all other members of the Cortex-M family, these cores includes the single precision variant of the ARMv7-M Floating-Point Unit (Fpv4-SP). The hardware FPU implementation adds an extra floating-point register bank consisting of S0–S31 and some other FPU registers. This FPU register set represents additional context that need to be preserved across interrupts and thread switching (e.g., in the preemptive QK kernel).
|
||
|
||
The ARM VFP has a very interesting feature called **lazy stacking** [@ref ARM-AN298]. This feature avoids an increase of interrupt latency by skipping the stacking of floating-point registers, if not required, that is:
|
||
|
||
- if the interrupt handler does not use the FPU, or
|
||
- if the interrupted program does not use the FPU.
|
||
|
||
If the interrupt handler has to use the FPU and the interrupted context has also previously used by the FPU, then the stacking of floating-point registers takes place at the point in the program where the interrupt handler first uses the FPU. The lazy stacking feature is programmable and by default it is turned ON.
|
||
|
||
@note
|
||
All built-in kernels in QP are designed to take advantage of the lazy stacking feature [@ref ARM-AN298].
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_ref References
|
||
|
||
@anchor ARM-AN298
|
||
- **[ARM AN298]** <a href="http://infocenter.arm.com/help/topic/com.arm.doc.dai0298a/DAI0298A_cortex_m4f_lazy_stacking_and_context_switching.pdf" target="_blank" class="extern">ARM Application Note 298 "Cortex-M4(F) Lazy Stacking and Context Switching", ARM 2012</a>
|
||
|
||
@anchor ARM-EPM-064408
|
||
- **[ARM-EPM-064408]** <a href="http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.epm064408/index.html" target="_blank" class="extern">"ARM Processor Cortex-M7 (AT610) and Cortex-M7 with FPU (AT611) Software Developers Errata Notice"</a>
|
||
|
||
@anchor Reminder
|
||
- **[Reminder]** <a href="https://state-machine.com/doc/Pattern_Reminder.pdf" target="_blank" class="extern">"Reminder State Pattern"</a>
|
||
|
||
@next{arm-cm_qv}
|
||
*/
|
||
/*##########################################################################*/
|
||
/*! @page arm-cm_qv Cooperative QV Kernel
|
||
|
||
@tableofcontents
|
||
|
||
<p>The non-preemptive, cooperative QV kernel executes active objects one at a time, with priority-based scheduling performed after run-to-completion (RTC) processing of each event. Due to naturally short duration of event processing in state machines, the simple QV kernel is often adequate for many real-time systems.
|
||
</p>
|
||
|
||
@ref
|
||
Long RTC steps can be often broken into shorter pieces by means of the "Reminder" state pattern [@ref Reminder].
|
||
|
||
|
||
In the QV port, the only components requiring platform-specific porting are QF and QV itself. The other two components: QEP and QS require merely recompilation and will not be discussed here. With the QV port you’re not using the QK or QXK kernels. The QV port to ARM Cortex-M is located in the folder <span class="img folder">/ports/arm-cm/qv/</span>.
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qv-synopsis Synopsis of the QV Port on ARM Cortex-M
|
||
The cooperative QV kernel works essentially as the traditional foreground-background system (a.k.a. “superloop”) in that all active objects are executed in the main loop and interrupts always return back to the point of preemption. To avoid race conditions between the main loop and the interrupts, QV briefly disables interrupts.
|
||
|
||
1. The ARM Cortex-M processor executes application code (the main loop) in the Privileged Thread mode, which is exactly the mode entered out of reset.
|
||
|
||
2. The exceptions (including all interrupts) are always processed in the Privileged Handler mode.
|
||
|
||
3. QV uses only the Main Stack Pointer. The Process Stack Pointer is not used and is not initialized.
|
||
|
||
4. ARM Cortex-M enters interrupt context without disabling interrupts (without setting the PRIMASK bit or the BASEPRI register). Generally, you should not disable interrupts inside your ISRs. In particular, the QP services QF_PUBLISH(), QF_TICK_X(), and QACTIVE_POST() should be called with interrupts enabled, to avoid nesting of critical sections.
|
||
> NOTE: If you don’t wish an interrupt to be preempted by another interrupt, you can always prioritize that interrupt in the NVIC to a higher level – use a lower numerical value of priority.
|
||
|
||
5. The QF_init() function calls the function QV_init() to set the interrupt priority of all IRQs available in the MCU to the safe value of QF_BASEPRI (for ARM-v7 architecture).
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qv-qep_port The qep_port.h Header File
|
||
The QEP header file for the ARM Cortex-M port is located in <span class="img file_h">/ports/arm-cm/qv/gnu/qep_port.h/</span>. The following shows the @c qep_port.h header file for ARM Cortex-M/GNU. The GNU-ARM compiler is a standard C99 compiler, so it simply includes the @c <stdint.h> header file that defines the platform-specific exact-with integer types.
|
||
|
||
@anchor arm-cm_qv_qep_port-code
|
||
<b>Listing: The qep_port.h header file for ARM Cortex-M</b>
|
||
@code{c}
|
||
#include <stdint.h> /* Exact-width types. WG14/N843 C99 Standard */
|
||
#include <stdbool.h> /* Boolean type. WG14/N843 C99 Standard */
|
||
#include "qep.h" /* QEP platform-independent public interface */
|
||
@endcode
|
||
|
||
@note
|
||
If you use a pre-C99 compiler (e.g., a C89 compiler), you can provide the @c stdint.h and @c stdbool.h header files yourself, or you can define the standard integer types and Boolean types directly in the @c qep_port.h header file.
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qv-qf_port The qf_port.h Header File
|
||
The QF header file for the ARM Cortex-M port is located in <span class="img file_h">/ports/arm-cm/qv/gnu/qf_port.h</span>. This file specifies the interrupt disabling policy (QF critical section) as well as the configuration constants for QF (see Chapter 8 in [PSiCC2]).
|
||
|
||
The ARM Cortex-M allows you to use the simplest “unconditional interrupt disabling” policy (see Section 7.3.2 in [PSiCC2]), because ARM Cortex-M is equipped with the standard nested vectored interrupt controller (NVIC) and generally runs ISRs with interrupts enabled (so the body of an ISR is not a critical section). The following listing shows the qf_port.h header file for ARM Cortex-M with the GNU-ARM toolset. Other toolsets use slightly different conditional compilation macros to select the Cortex-M variants, but implement the same policies.
|
||
|
||
@anchor arm-cm_qf_port_h-code
|
||
<b>Listing: The qf_port.h header file for ARM Cortex-M</b>
|
||
@code{c}
|
||
/* The maximum number of active objects in the application, see NOTE1 */
|
||
[1] #define QF_MAX_ACTIVE 32
|
||
|
||
/* The maximum number of system clock tick rates */
|
||
[2] #define QF_MAX_TICK_RATE 2
|
||
|
||
/* QF interrupt disable/enable and log2()... */
|
||
[3] #if (__ARM_ARCH == 6) /* Cortex-M0/M0+/M1(v6-M, v6S-M)? */
|
||
|
||
/* Cortex-M0/M0+/M1(v6-M, v6S-M) interrupt disabling policy, see NOTE2 */
|
||
[4] #define QF_INT_DISABLE() __asm volatile ("cpsid i")
|
||
[5] #define QF_INT_ENABLE() __asm volatile ("cpsie i")
|
||
|
||
/* QF critical section entry/exit (save and restore interrupt status) */
|
||
[6] #define QF_CRIT_STAT_TYPE uint32_t
|
||
[7] #define QF_CRIT_ENTRY(primask_) do { \
|
||
[8] QF_GET_PRIMASK((primask_)); \
|
||
[9] QF_INT_DISABLE(); \
|
||
} while (0)
|
||
[10] #define QF_CRIT_EXIT(primask_) QF_SET_PRIMASK((primask_))
|
||
|
||
/* CMSIS threshold for "QF-aware" interrupts, see NOTE2 and NOTE5 */
|
||
[11] #define QF_AWARE_ISR_CMSIS_PRI 0
|
||
|
||
/* macro for getting the PRIMASK register */
|
||
[12] #define QF_GET_PRIMASK(primask_) __asm volatile (\
|
||
"mrs %0,PRIMASK" : "=r" (primask_) :: )
|
||
|
||
/* macro for setting the PRIMASK register */
|
||
[13] #define QF_SET_PRIMASK(primask_) __asm volatile (\
|
||
"msr PRIMASK,%0" :: "r" (primask_) : )
|
||
|
||
[14] #else /* Cortex-M3/M4/M4F */
|
||
|
||
/* Cortex-M3/M4/M7 alternative interrupt disabling with PRIMASK */
|
||
[15] #define QF_PRIMASK_DISABLE() __asm volatile ("cpsid i")
|
||
[16] #define QF_PRIMASK_ENABLE() __asm volatile ("cpsie i")
|
||
|
||
/* Cortex-M3/M4/M7 interrupt disabling policy, see NOTE3 and NOTE4 */
|
||
[17] #define QF_INT_DISABLE() do { \
|
||
[18] QF_PRIMASK_DISABLE(); \
|
||
[19] QF_SET_BASEPRI(QF_BASEPRI); \
|
||
[20] QF_PRIMASK_ENABLE(); \
|
||
} while (0)
|
||
[21] #define QF_INT_ENABLE() QF_SET_BASEPRI(0U)
|
||
|
||
/* QF critical section entry/exit (save and restore interrupt status) */
|
||
[22] #define QF_CRIT_STAT_TYPE unsigned long
|
||
[23] #define QF_CRIT_ENTRY(basepri_) do {\
|
||
[24] QF_GET_BASEPRI((basepri_)); \
|
||
[25] QF_INT_DISABLE(); \
|
||
} while (0)
|
||
[26] #define QF_CRIT_EXIT(basepri_) QF_SET_BASEPRI((basepri_))
|
||
|
||
/* BASEPRI threshold for "QF-aware" interrupts, see NOTE3.
|
||
* CAUTION: keep in synch with the value defined in "qk_port.s"
|
||
*/
|
||
[27] #define QF_BASEPRI (0xFFU >> 2)
|
||
|
||
/* CMSIS threshold for "QF-aware" interrupts, see NOTE5 */
|
||
[28] #define QF_AWARE_ISR_CMSIS_PRI (QF_BASEPRI >> (8 - __NVIC_PRIO_BITS))
|
||
|
||
/* Cortex-M3/M4/M7 provide the CLZ instruction for fast LOG2 */
|
||
[29] #define QF_LOG2(n_) ((uint_fast8_t)(32U - __builtin_clz(n_)))
|
||
|
||
/* macro for getting the BASEPRI register */
|
||
[30] #define QF_GET_BASEPRI(basepri_) __asm volatile (\
|
||
"mrs %0,BASEPRI" : "=r" (basepri_) :: )
|
||
|
||
#endif
|
||
|
||
[31] #define QF_CRIT_EXIT_NOP() __asm volatile ("isb")
|
||
|
||
#include "qep_port.h" /* QEP port */
|
||
#include "qv_port.h" /* QV port cooperative kernel port */
|
||
#include "qf.h" /* QF platform-independent public interface */
|
||
@endcode
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">1</span>
|
||
The #QF_MAX_ACTIVE specifies the maximum number of active object priorities in the application. You always need to provide this constant. Here, #QF_MAX_ACTIVE is set to 32, but it can be increased up to the maximum limit of 63 active object priorities in the system.
|
||
|
||
> NOTE: The @c qf_port.h header file does not change the default settings for all the rest of various object sizes inside QF. Please refer to Chapter 8 of [PSiCC2] for discussion of all configurable QF parameters.
|
||
</li>
|
||
<li><span class="tag">2</span>
|
||
The #QF_MAX_TICK_RATE specifies the maximum number of clock tick rates for QP time events. If you don't need to specify this limit, in which case the default of a single clock rate will be chosen.
|
||
</li>
|
||
<li><span class="tag">3</span>
|
||
As described in the previous @ref arm-cm_int "Section", the interrupt disabling policy for the ARMv6-M architecture (Cortex-M0/M0+) is different than the policy for the ARMv7-M. The macro __ARM_ARCH is defined as 6 for the ARMv6-M architecture (Cortex-M0/M0+), and 7 for ARMv7-M (Cortex-M3/M4/M4F).
|
||
</li>
|
||
<li><span class="tag">4-5</span>
|
||
For the ARMv6-M architecture, the interrupt disabling policy uses the PRIMASK register to disable interrupts globally. The @c QF_INT_DISABLE() macro resolves in this case to the inline assembly instruction “CPSD i”, which sets the PRIMASK. The @c QF_INT_ENABLE() macro resolves to the inline assembly instruction “CPSE i”, which clears the PRIMASK.
|
||
</li>
|
||
<li><span class="tag">6</span>
|
||
The #QF_CRIT_STAT_TYPE is defined, meaning that the critical section uses the policy of “saving and restoring interrupt status” policy.
|
||
</li>
|
||
<li><span class="tag">7</span>
|
||
The QF_CRIT_ENTRY() enters a critical section.
|
||
</li>
|
||
<li><span class="tag">8</span>
|
||
The value of Cortex-M PRIMASK register is retrieved and stored in the @c primask_ argument.
|
||
</li>
|
||
<li><span class="tag">9</span>
|
||
Interrupts are disabled by setting the PRIMASK register.
|
||
</li>
|
||
<li><span class="tag">10</span>
|
||
The QF_CRIT_EXIT() macro restores the PRIMASK register value for the @c primask_ argument.
|
||
</li>
|
||
<li><span class="tag">11</span>
|
||
For the ARMv6-M architecture, the QF_AWARE_ISR_CMSIS_PRI priority level is defined as zero, meaning that all interrupts are "kernel-aware", because all interrupt priorities are disabled by the kernel.
|
||
</li>
|
||
<li><span class="tag">12</span>
|
||
The @c QF_GET_PRIMASK() macro uses the inline assembly to get the PRIMASK register.
|
||
</li>
|
||
<li><span class="tag">13</span>
|
||
The @c QF_SET_PRIMASK() macro uses the inline assembly to set the PRIMASK register.
|
||
</li>
|
||
<li><span class="tag">14</span>
|
||
For the ARMv7-M (Cortex-M3/M4/M4F) architecture...
|
||
</li>
|
||
<li><span class="tag">15</span>
|
||
The @c QF_PRIMASK_DISABLE() macro resolves to the inline assembly instruction “CPSD i”, which sets the PRIMASK.
|
||
</li>
|
||
<li><span class="tag">16</span>
|
||
The @c QF_PRIMASK_ENABLE() macro resolves to the inline assembly instruction “CPSE i”, which clears the PRIMASK.
|
||
</li>
|
||
<li><span class="tag">17</span>
|
||
As described in previous @ref arm-cm_int "Section", the @c QF_INT_DISABLE() macro for the ARMv7-M architecture (Cortex-M3/M4/M4F) uses the BASEPRI register.
|
||
</li>
|
||
<li><span class="tag">18</span>
|
||
Before setting the BASEPRI register, interrupts are disabled with the PRIMASK register, which is the recommended workaround for the Cortex-M7 r0p1 hardware bug, as described in the ARM Ltd. [@ref ARM-EPM-064408], Erratum 837070.
|
||
</li>
|
||
<li><span class="tag">19</span>
|
||
The QF_SET_BASEPRI() macro sets the BASEPRI register to the value QF_BASEPRI, which selectively disables the “kernel aware” interrupts. (NOTE: this method can never disable interrupt of priority 0 (highest)).
|
||
</li>
|
||
<li><span class="tag">20</span>
|
||
After setting the BASEPRI register, interrupts are re-anabed with the PRIMASK register, which is the recommended workaround for the Cortex-M7 r0p1 hardware bug, as described in the ARM Ltd. [@ref ARM-EPM-064408], Erratum 837070.
|
||
</li>
|
||
<li><span class="tag">21</span>
|
||
The QF_INT_ENABLE() macro sets the BASEPRI register to zero, which disables BASEPRI interrupt masking.
|
||
</li>
|
||
<li><span class="tag">22</span>
|
||
The QF_CRIT_STAT_TYPE is defined, meaning that the critical section uses the policy of “saving and restoring interrupt status” policy (see also step (6) for the ARMv7-M architecture).
|
||
</li>
|
||
<li><span class="tag">23</span>
|
||
The QF_CRIT_ENTRY() enters a critical section.
|
||
</li>
|
||
<li><span class="tag">24</span>
|
||
The value of the BASEPRI register is retrieved and stored in the @c basepri_ argument.
|
||
</li>
|
||
<li><span class="tag">25</span>
|
||
Interrupts are disabled by setting the BASEPRI register.
|
||
</li>
|
||
<li><span class="tag">26</span>
|
||
The QF_CRIT_EXIT() macro restores the BASEPRI register value for the @c basepri_ argument.
|
||
</li>
|
||
<li><span class="tag">27</span>
|
||
The @c QF_BASEPRI value is defined such that it is the lowest priority for the minimum number of 3 priority-bits that the ARM7-M architecture must provide. This partitions the interrupts as “kernel-unaware” and “kernel-aware” interrupts, as shown in section @ref arm-cm_int-assign.
|
||
</li>
|
||
<li><span class="tag">28</span>
|
||
For the ARMv7-M architecture, the @c QF_AWARE_ISR_CMSIS_PRI priority level suitable for the CMSIS function @c NVIC_SetPriority() is determined by the @c QF_BASEPRI value.
|
||
</li>
|
||
<li><span class="tag">29</span>
|
||
The macro @c QF_LOG2() is defined to take advantage of the CLZ instruction (Count Leading Zeroes), which is available in the ARMv7-M architecture.
|
||
|
||
> NOTE: The CLZ instruction is not implemented in the ARMv6M architecture. If the @c QF_LOG2() macro is not defined, the QP framework will use the log2 implementation based on a lookup table.
|
||
</li>
|
||
<li><span class="tag">30</span>
|
||
The @c QF_GET_BASEPRI() macro is defined in using in-line assembly
|
||
</li>
|
||
<li><span class="tag">31</span>
|
||
The macro @c QF_CRIT_EXIT_NOP() provides the protection against merging two critical sections occurring back-to-back in the QP code.
|
||
</li>
|
||
</ul>
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qv_port_h The qv_port.h Header File
|
||
The QV header file for the ARM Cortex-M port is located in <span class="img file_h">/ports/arm-cm/qv/gnu/qv_port.h</span>. This file provides the macro QV_CPU_SLEEP(), which specifies how to enter the CPU sleep mode safely in the cooperative QV kernel (see also Section 4.7) and [Samek 07]).
|
||
|
||
@note
|
||
To avoid race conditions between interrupts waking up active objects and going to sleep, the cooperative QV kernel calls the QV_CPU_SLEEP() callback with interrupts disabled.
|
||
|
||
|
||
@anchor arm-cm_qv_port_h-code
|
||
<b>Listing: The qv_port.h header file for ARM Cortex-M</b>
|
||
@code{c}
|
||
#ifdef ARM_ARCH_V6M /* Cortex-M0/M0+/M1 ? */
|
||
|
||
[1] #define QV_CPU_SLEEP() do { \
|
||
__asm volatile ("wfi"); \
|
||
QF_INT_ENABLE(); \
|
||
} while (0)
|
||
|
||
#else /* Cortex-M3/M4/M4F */
|
||
|
||
[2] #define QV_CPU_SLEEP() do { \
|
||
QF_PRIMASK_DISABLE(); \
|
||
QF_INT_ENABLE(); \
|
||
__asm volatile ("wfi"); \
|
||
QF_RIMASK_ENABLE(); \
|
||
} while (0)
|
||
|
||
#endif
|
||
|
||
[3] #define QV_INIT() QV_init()
|
||
void QV_init();
|
||
|
||
#include "qv.h" /* QV platform-independent public interface */
|
||
|
||
@endcode
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">1</span>
|
||
For the ARMv6-M architecture, the macro QV_CPU_SLEEP() only stops the CPU with the WFI instruction (Wait For Interrupt). After the CPU is woken up by an interrupt, interrupts are re-enabled with the PRIMASK.
|
||
</li>
|
||
<li><span class="tag">2</span>
|
||
For the ARMv7-M architecture, the macro QV_CPU_SLEEP() first disables interrupts by setting the PRIMASK, then clears the BASEPRI to enable all “kernel-aware” interrupts and only then stops the CPU with the WFI instruction (Wait For Interrupt). After the CPU is woken up by an interrupt, interrupts are re-enabled with the PRIMASK. This sequence is necessary, because the ARM Cortex-M3/M4 cores cannot be woken up by any interrupt blocked by the BASEPRI register.
|
||
</li>
|
||
<li><span class="tag">2</span>
|
||
The macro QV_INIT() is defined as a call to the QV_init() function, which means that this function will be called from QF_init(). The QV_init() function initializes all available IRQ priorities in the MCU to the safe value of QF_BASEPRI.
|
||
</li>
|
||
</ul>
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qv_port_c The qv_port.c Implementation File
|
||
The QV implementation file for the ARM Cortex-M port is located in <span class="img file_c">/ports/arm-cm/qv/gnu/qf_port.c</span>. This file defines the function @c QV_init(), which for the ARMv7-M architecture sets the interrupt priorities of all IRQs to the safe value `QF_BASEPRI`.
|
||
|
||
@anchor arm-cm_qv_port_c-code
|
||
<b>Listing: The qv_port.c header file for ARM Cortex-M</b>
|
||
@code{c}
|
||
#include "qf_port.h"
|
||
|
||
[1] #if (__ARM_ARCH != 6) /* NOT Cortex-M0/M0+/M1 ? */
|
||
|
||
#define SCnSCB_ICTR ((uint32_t volatile *)0xE000E004)
|
||
#define SCB_SYSPRI ((uint32_t volatile *)0xE000ED14)
|
||
#define NVIC_IP ((uint32_t volatile *)0xE000E400)
|
||
|
||
void QV_init(void) {
|
||
uint32_t n;
|
||
|
||
/* set exception priorities to QF_BASEPRI...
|
||
* SCB_SYSPRI1: Usage-fault, Bus-fault, Memory-fault
|
||
*/
|
||
[2] SCB_SYSPRI[1] |= (QF_BASEPRI << 16) | (QF_BASEPRI << 8) | QF_BASEPRI;
|
||
|
||
/* SCB_SYSPRI2: SVCall */
|
||
[3] SCB_SYSPRI[2] |= (QF_BASEPRI << 24);
|
||
|
||
/* SCB_SYSPRI3: SysTick, PendSV, Debug */
|
||
[4] SCB_SYSPRI[3] |= (QF_BASEPRI << 24) | (QF_BASEPRI << 16) | QF_BASEPRI;
|
||
|
||
/* set all implemented IRQ priories to QF_BASEPRI... */
|
||
[5] n = 8 + (*SCnSCB_ICTR << 3); /* # interrupt priority registers */
|
||
do {
|
||
--n;
|
||
[6] NVIC_IP[n] = (QF_BASEPRI << 24) | (QF_BASEPRI << 16)
|
||
| (QF_BASEPRI << 8) | QF_BASEPRI;
|
||
} while (n != 0);
|
||
}
|
||
|
||
#endif /* NOT Cortex-M0/M0+/M1 */
|
||
@endcode
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">1</span>
|
||
For the ARMv7-M architecture (Cortex-M3/M4/M7)...
|
||
</li>
|
||
<li><span class="tag">2</span>
|
||
The exception priorities for User-Fault, Bus-Fault, and Mem-Fault are set to the value QF_BASEPRI.
|
||
</li>
|
||
<li><span class="tag">3</span>
|
||
The exception priority for SVCCall is set to the value QF_BASEPRI.
|
||
</li>
|
||
<li><span class="tag">4</span>
|
||
The exception priority for SysTick, PendSV, and Debug is set to the value QF_BASEPRI.
|
||
</li>
|
||
<li><span class="tag">5</span>
|
||
The number of implemented IRQs is read from the @c SCnSCB_ICTR register
|
||
</li>
|
||
<li><span class="tag">6</span>
|
||
The interrupt priority of all implemented IRQs is set to the safe value QF_BASEPRI in a loop.
|
||
</li>
|
||
</ul>
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qv-isr Writing ISRs for QV
|
||
The ARM Cortex-M CPU is designed to use regular C functions as exception and interrupt service routines (ISRs).
|
||
|
||
@note
|
||
The ARM EABI (Embedded Application Binary Interface) requires the stack be 8-byte aligned, whereas some compilers guarantee only 4-byte alignment. For that reason, some compilers (e.g., GNU-ARM) provide a way to designate ISR functions as interrupts. For example, the GNU-ARM compiler provides the __attribute__((__interrupt__)) designation that will guarantee the 8-byte stack alignment.
|
||
|
||
|
||
Typically, ISRs are application-specific (with the main purpose to produce events for active objects). Therefore, ISRs are not part of the generic QP port, but rather part of the BSP (Board Support Package).
|
||
|
||
The following listing shows an example of the SysTick_Handler() ISR (from the DPP example application). This ISR calls the QF_TICK_X() macro to perform QF time-event management.
|
||
|
||
@anchor arm-cm_qv-isr-code
|
||
<b>Listing: An ISR header for QV</b>
|
||
@code{c}
|
||
void SysTick_Handler(void) __attribute__((__interrupt__));
|
||
void SysTick_Handler(void) {
|
||
~ ~ ~
|
||
QF_TICK_X(0U, &l_SysTick_Handler); /* process all armed time events */
|
||
}
|
||
@endcode
|
||
|
||
@note
|
||
The QP port to ARM Cortex-M complies with the CMSIS standard, which dictates the names of all exception handlers and IRQ handlers.
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qv-fpu Using the FPU in the QV Port (Cortex-M4F/M7)
|
||
If you have the Cortex-M4F CPU and your application uses the hardware FPU, it should be enabled because it is turned off out of reset. The CMSIS-compliant way of turning the FPU on looks as follows:
|
||
|
||
@verbatim
|
||
SCB->CPACR |= (0xFU << 20);
|
||
@endverbatim
|
||
|
||
@note
|
||
The FPU must be enabled before executing any floating point instruction. An attempt to execute a floating point instruction will fault if the FPU is not enabled.
|
||
|
||
Depending on wheter or not you use the FPU in your ISRs, the “Vanilla” QP port allows you to configure the FPU in various ways, as described in the following sub-sections.
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_qv-fpu_noisr FPU NOT used in the ISRs
|
||
If you use the FPU only at the thread-level (inside active objects) and none of your ISRs use the FPU, you can setup the FPU not to use the automatic state preservation and not to use the lazy stacking feature as follows:
|
||
|
||
@verbatim
|
||
FPU->FPCCR &= ~((1U << FPU_FPCCR_ASPEN_Pos) | (1U << FPU_FPCCR_LSPEN_Pos));
|
||
@endverbatim
|
||
|
||
With this setting, the Cortex-M4F processor handles the ISRs in the exact-same way as Cortex-M0-M3, that is, only the standard interrupt frame with R0-R3,R12,LR,PC,xPSR is used. This scheme is the fastest and incurs no additional CPU cycles to save and restore the FPU registers.
|
||
|
||
@note
|
||
This FPU setting will lead to FPU errors, if any of the ISRs indeed starts to use the FPU
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_qv-fpu_isr FPU used in the ISRs
|
||
If you use the FPU both at the thread-level (inside active objects) and in any of your ISRs as well, you should setup the FPU to use the automatic state preservation and the lazy stacking feature as follows:
|
||
|
||
@verbatim
|
||
FPU->FPCCR |= (1U << FPU_FPCCR_ASPEN_Pos) | (1U << FPU_FPCCR_LSPEN_Pos);
|
||
@endverbatim
|
||
|
||
This will enable the lazy stacking feature of the Cortex-M4F/M7 processor [@ref ARM-AN298]. The the "automatic state saving" and "lazy stacking" are enabled by default, so you typically don't need to change these settings.
|
||
|
||
@note
|
||
As described in the ARM Application Note “Cortex-M4(F) Lazy Stacking and Context Switching” [@ref ARM-AN298], the FPU automatic state saving requires more stack plus additional CPU time to save the FPU registers, but only when the FPU is actually used.
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qv-idle QV Idle Processing Customization in QV_onIdle()
|
||
When no events are available, the non-preemptive QV kernel invokes the platform-specific callback function QV_onIdle(), which you can use to save CPU power, or perform any other “idle” processing (such as Quantum Spy software trace output).
|
||
|
||
@note
|
||
The idle callback QV_onIdle() must be invoked with interrupts disabled, because the idle condition can be changed by any interrupt that posts events to event queues. QV_onIdle() must internally enable interrupts, ideally atomically with putting the CPU to the power-saving mode (see also [Samek 07] and Chapter 7 in [PSiCC2]).
|
||
|
||
|
||
Because QV_onIdle() must enable interrupts internally, the signature of the function depends on the interrupt locking policy. In case of the simple “unconditional interrupt locking and unlocking” policy, which is used in this ARM Cortex-M port, the QV_onIdle() takes no parameters. Listing 6 shows an example implementation of QV_onIdle() for the TM4C MCU. Other ARM Cortex-M embedded microcontrollers (e.g., NXP’s LPC1114/1343) handle the power-saving mode very similarly.
|
||
|
||
@anchor arm-cm_qv_onidle-code
|
||
<b>Listing: QV_onIdle() for ARM Cortex-M</b>
|
||
@code{c}
|
||
|
||
[1] void QV_onIdle(void) { /* entered with interrupts DISABLED, see NOTE01 */
|
||
~ ~ ~
|
||
[2] #if defined NDEBUG
|
||
/* Put the CPU and peripherals to the low-power mode */
|
||
[3] QV_CPU_SLEEP(); /* atomically go to sleep and enable interrupts */
|
||
#else
|
||
[4] QF_INT_ENABLE(); /* just enable interrupts */
|
||
#endif
|
||
}
|
||
|
||
@endcode
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">1</span>
|
||
The cooperative QV kernel calls the QV_onIdle() callback with interrupts disabled, to avoid race condition with interrupts that can post events to active objects and thus invalidate the idle condition.
|
||
<li>
|
||
<li><span class="tag">2</span>
|
||
The sleep mode is used only in the non-debug configuration, because sleep mode stops CPU clock, which can interfere with debugging.
|
||
<li>
|
||
<li><span class="tag">3</span>
|
||
The macro QV_CPU_SLEEP() is used to put the CPU to the low-power sleep mode safely. The macro QV_CPU_SLEEP() is defined in the qv_port.h header file for the QV kernel and depends on the interrupt disabling policy used.
|
||
<li>
|
||
<li><span class="tag">4</span>
|
||
When a sleep mode is not used, the QV_onIdle() callback simply re-enables interrupts.
|
||
</li>
|
||
</ul>
|
||
|
||
@next{arm-cm_qk}
|
||
*/
|
||
/*##########################################################################*/
|
||
/*! @page arm-cm_qk Preemptive Run-to-Completion QK Kernel
|
||
|
||
@tableofcontents
|
||
|
||
<p>The preemptive, run-to-completion (RTC) QK kernel breaks entirely with the endless-loop structure of the thread routines and instead uses threads structured as one-shot, discrete, run-to-completion functions, very much like ISRs [PSiCC2, Chapter 10]. In fact, the QK kernel views interrupts very much like threads of a “super-high” priority, except that interrupts are prioritized in hardware by the interrupt controller, whereas threads are prioritized in software by the RTC kernel.
|
||
</p>
|
||
|
||
@note
|
||
The preemptive QK kernel uses only one stack for all threads (see the next section), but the stack must be bigger than in the non-preemptive QV kernel. You need to adjust the size of this stack to be large enough for your application.
|
||
|
||
|
||
As a fully preemptive multithreading kernel, QK must ensure that at all times the CPU executes the highest-priority thread (active object) that is ready to run. Fortunately, only two scenarios can lead to readying a higher-priority thread:
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">1</span>
|
||
When a lower-priority thread posts an event to a higher-priority thread, the kernel must immediately suspend the execution of the lower-priority thread and start the higher-priority thread. This type of preemption is called <b>synchronous preemption</b> because it happens synchronously with posting an event to the thread's event queue.
|
||
> NOTE: The stack usage shown in the bottom panel displays stack growing down (towards lower addresses), as it is the case in ARM Cortex-M.
|
||
</li>
|
||
</ul>
|
||
|
||
@anchor arm-cm_qk-synch-fig
|
||
@image html qk_synch.gif "Synchronous Preemption in QK"
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">2</span>
|
||
When an interrupt posts an event to a higher-priority thread than the interrupted thread, upon completion of the ISR the kernel must start execution of the higher-priority thread instead of resuming the lower-priority thread. This type of preemption is called <b>asynchronous preemption</b> because it can happen asynchronously, any time interrupts are not explicitly disabled.
|
||
> NOTE: The stack usage during asynchronous preemption on ARM Cortex-M is slightly simplified in the diagram below. A more detailed stack usage diagram is discussed later in the section explaining the @ref "Detailed stack allocation in QK for ARM Cortex-M".
|
||
</li>
|
||
</ul>
|
||
|
||
@anchor arm-cm_qk-asynch-fig
|
||
@image html qk_asynch.gif "Synchronous Preemption in QK"
|
||
|
||
|
||
@note
|
||
A traditional RTOS kernel does not distinguish between the synchronous and asynchronous preemptions and makes all preemptions look like the more stack-intensive asynchronous preemptions. In contrast, a RTC kernel can implement synchronous preemption as a simple function call (to QK_activate_()), which is much more efficient than a full context-switch.
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qk-synopsis Synopsis of the QK Port on ARM Cortex-M
|
||
The ARM Cortex-M architecture is designed primarily for the traditional real-time kernels that use multiple per-thread stacks. This section explains how the run-to-completion preemptive QK kernel works on ARM Cortex-M.
|
||
|
||
1. The ARM Cortex-M processor executes the QK application code (active objects) in the Privileged Thread mode, which is exactly the mode entered out of reset. The exceptions (including all interrupts) are always processed in the Privileged Handler mode.
|
||
|
||
2. QK uses only the Main Stack Pointer (QK is a single stack kernel). The Process Stack Pointer is not used and is not initialized.
|
||
|
||
3. ARM Cortex-M enters interrupt context without disabling interrupts (without setting the PRIMASK bit or the BASEPRI register). Generally, you should not disable interrupts inside your ISRs. In particular, the QP services QF_PUBLISH(), QF_TICK_X(), and QACTIVE_POST() should be called with interrupts enabled, to avoid nesting of critical sections. (NOTE: If you don’t wish an interrupt to be preempted by another interrupt, you can always prioritize that interrupt in the NVIC to a higher level -- use a lower numerical value of priority).
|
||
|
||
|
||
4. The QK port uses the PendSV exception (number 14) and the NMI exception (number 2) to perform asynchronous preemption and return to the preempted thread, respectively (see Chapter 10 in [<a href="https://state-machine.com/psicc2/" target="_blank" class="extern">PSiCC2</a>]). The startup code must initialize the Interrupt Vector Table with the addresses of @c PendSV_Handler() and @c NMI_Handler() exception handlers.
|
||
|
||
5. The QF_init() function calls the function QK_init() to set the priority of the PendSV exception to the lowest level in the whole system (0xFF). The function QK_init() additionally sets the interrupt priority of all IRQs available in the MCU to the safe value of @c QF_BASEPRI (for ARM-v7 architecture).
|
||
|
||
6. It is strongly recommended that you do not assign the lowest priority (0xFF) to any interrupt in your application. With 3 MSB-bits of priority, this leaves the following 7 priority levels for you (listed from the lowest to the highest urgency): 0xC0, 0xA0, 0x80, 0x60, 0x40, 0x20, and 0x00 (the highest priority).
|
||
|
||
7. Before returning, every “kernel aware” ISR must check whether an active object has been activated that has a higher priority than the currently running active object. If this is the case, the ISR must set the PensSV pending flag in the NVIC. All this is accomplished in the macro QK_ISR_EXIT(), which must be called just before exiting every ISRs.
|
||
|
||
8. In ARM Cortex-M the whole prioritization of interrupts, including the PendSV exception, is performed entirely by the NVIC. Because the PendSV has the lowest priority in the system, the NVIC tail-chains to the PendSV exception only after exiting the last nested interrupt.
|
||
|
||
9. 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 the NMI's return.
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_qk-preempt Preemption Scenarios in QK on ARM Cortex-M
|
||
|
||
@anchor arm-cm_qk-arm-cm-fig
|
||
@image html qk_arm-cm.gif "Several preemption scenarios in QK"
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">0</span>
|
||
The timeline begins with the QK executing the idle loop.
|
||
</li>
|
||
<li><span class="tag">1</span>
|
||
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.
|
||
</li>
|
||
<li><span class="tag">2</span>
|
||
The ISR performs its work, and in QK always must call the QK_ISR_EXIT() macro, which calls the QK scheduler to determine if there is a higher-priority AO to run. If so, the macro sets the pending flag for the PendSV exception in the NVIC. The priority of the PendSV exception is configured to be the lowest of all exceptions, so the ISR continues executing and PendSV exception remains pending. At the ISR return, the ARM Cortex-M CPU performs tail-chaining to the pending PendSV exception.
|
||
</li>
|
||
<li><span class="tag">3</span>
|
||
The PendSV exception discovers whether there is a higher-priority thread to run then the preempted one (by calling QK_schedPrio_()) and if so, to to synthesize an exception stack frame to return to the QK scheduler (QK_sched_()) to run this new thread.
|
||
> NOTE: The QK scheduler must run in the thread context, while PendSV executes in the exception context. The change of the context is accomplished by returning from the PendSV exception directly to the QK scheduler.
|
||
|
||
To return directly to the QK scheduler, PendSV synthesizes an exception stack frame, which contains the exception return address set to QK_sched_(). The QK scheduler discovers that the Low-priority thread is ready to run (the ISR has posted event to this thread). The QK scheduler enables interrupts and launches the Low-priority thread, which is simply a C-function call in QK. The Low-priority thread (active object) starts running.
|
||
</li>
|
||
<li><span class="tag">4</span>
|
||
Some time later a low-priority interrupt occurs. The Low-priority thread is suspended and the CPU pushes the interrupt stack frame to the Main Stack and starts executing the ISR.
|
||
</li>
|
||
<li><span class="tag">5</span>
|
||
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.
|
||
</li>
|
||
<li><span class="tag">6</span>
|
||
The High-priority ISR sets the pending flag for the PendSV exception by means of the QK_ISR_EXIT() macro. When the High-priority ISR returns, the NVIC does not tail-chain to the PendSV exception, because a higher-priority ISR than PendSV is still active. The NVIC performs an exception return to the preempted Low-priority interrupt, which finally completes.
|
||
</li>
|
||
<li><span class="tag">7</span>
|
||
Upon the exit from the Low-priority ISR, it too sets the pending flag for the PendSV exception by means of the QK_ISR_EXIT() macro. The PendSV is already pended from the High-priority interrupt, so pending is again is redundant, but it is not an error. At the ISR return, the ARM Cortex-M CPU performs tail-chaining to the pending PendSV exception.
|
||
</li>
|
||
<li><span class="tag">8</span>
|
||
The PendSV exception synthesizes an interrupt stack frame to return to the QK scheduler. The QK scheduler detects that the High-priority thread is ready to run and launches the High-priority thread (normal C-function call). The High-priority thread runs to completion and returns to the scheduler.
|
||
</li>
|
||
<li><span class="tag">9</span>
|
||
The QK scheduler does not find any more higher-priority threads to execute and needs to return to the preempted thread. The only way to restore the interrupted context in ARM Cortex-M is through the interrupt return, but the thread is executing outside of the interrupt context (in fact, threads are executing in the Privileged Thread mode). The thread enters the Handler mode by pending the NMI exception.
|
||
> NOTE: The NMI exception is pended while interrupts are still disabled. This is not a problem, because NMI cannot be masked by disabling interrupts, so runs without any problems.
|
||
</li>
|
||
<li><span class="tag">10</span>
|
||
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 thread preemption.
|
||
</li>
|
||
<li><span class="tag">11</span>
|
||
The Low-priority thread, which has been preempted all that time, resumes and finally runs to completion and returns to the QK scheduler. The QK scheduler does not find any more threads to launch and causes the NMI exception to return to the preempted thread.
|
||
</li>
|
||
<li><span class="tag">12</span>
|
||
The NMI exception discards its own interrupt stack frame and returns using the interrupt stack frame from the preempted thread context
|
||
</li>
|
||
</ul>
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qk-qf_port The qf_port.h Header File
|
||
The QF header file for the ARM Cortex-M port is located in <span class="img file_h">/ports/arm-cm/qk/gnu/qf_port.h</span>. This file is almost identical to the @ref arm-cm_qv-qf_port "QV port", except the header file in the QK port includes `qk_port.h` header file instead of `qv_porth`.
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qk-qk_port The qk_port.h Header File
|
||
You configure and customize QK through the header file qk_port.h, which is located in the QP ports directory <span class="img folder">/ports/arm-cm/qk/gnu/</span>. The most important function of <span class="img file_h">qk_port.h</span> is specifying interrupt entry and exit.
|
||
|
||
@note
|
||
As any preemptive kernel, QK needs to be notified about entering the interrupt context and about exiting an interrupt context in order to perform a context switch, if necessary.
|
||
|
||
|
||
@anchor arm-cm_qk_port-code
|
||
<b>Listing: qk_port.h header file for ARM Cortex-M</b>
|
||
@code{c}
|
||
/* determination if the code executes in the ISR context */
|
||
[1] #define QK_ISR_CONTEXT_() (QK_get_IPSR() != (uint32_t)0)
|
||
|
||
__attribute__((always_inline))
|
||
[2] static inline uint32_t QK_get_IPSR(void) {
|
||
uint32_t regIPSR;
|
||
__asm volatile ("mrs %0,ipsr" : "=r" (regIPSR));
|
||
return regIPSR;
|
||
}
|
||
|
||
/* QK interrupt entry and exit */
|
||
[3] #define QK_ISR_ENTRY() ((void)0)
|
||
|
||
[4] #define QK_ISR_EXIT() do { \
|
||
[5] QF_INT_DISABLE(); \
|
||
[6] if (QK_sched_() != (uint_fast8_t)0) { \
|
||
[7] (*Q_UINT2PTR_CAST(uint32_t, 0xE000ED04U) = (uint32_t)(1U << 28)); \
|
||
} \
|
||
[8] QF_INT_ENABLE(); \
|
||
} while (0)
|
||
|
||
/* initialization of the QK kernel */
|
||
[9] #define QK_INIT() QK_init()
|
||
void QK_init(void);
|
||
|
||
#include "qk.h" /* QK platform-independent public interface */
|
||
@endcode
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">1</span>
|
||
The macro @c QK_ISR_CONTEXT() returns true when the code executes in the ISR context and false otherwise. The macro takes advantage of the ARM Cortex-M register IPSR, which is non-zero when the CPU executes an exception (or interrupt) and is zero when the CPU is executing thread code.
|
||
> NOTE: QK needs to distinguish between ISR and thread contexts, because threads need to perform synchronous context switch (when a higher-priority thread becomes ready to run), while ISRs should not do that.
|
||
</li>
|
||
<li><span class="tag">2</span>
|
||
The inline function @c QK_get_IPSR() obtains the IPSR register and returns it to the caller. This function is defined explicitly for the GNU-ARM toolset, but many other toolsets provide this function as an intrinsic, built-in facility.
|
||
</li>
|
||
<li><span class="tag">3</span>
|
||
The @c QK_ISR_ENTRY() macro notifies QK about entering an ISR. The macro is empty, because the determination of the ISR vs thread context is performed independently in the @c QK_ISR_CONTEXT() macro (see above).
|
||
</li>
|
||
<li><span class="tag">4</span>
|
||
The @c QK_ISR_EXIT() macro notifies QK about exiting an ISR.
|
||
</li>
|
||
<li><span class="tag">5</span>
|
||
Interrupts are disabled before calling QK scheduler.
|
||
</li>
|
||
<li><span class="tag">6</span>
|
||
The QK scheduler is called to find out whether an active object of a higher priority than the current one needs activation. The @c QK_sched_() function returns non zero value if this is the case.
|
||
</li>
|
||
<li><span class="tag">7</span>
|
||
If asynchronous preemption becomes necessary, the code sets the PENDSV Pend bit(28) in the ICSR register (Interrupt Control and State Register). The register is mapped at address 0xE000ED04 in all ARM Cortex-M cores.
|
||
</li>
|
||
<li><span class="tag">8</span>
|
||
The interrupts are re-enabled after they have been disabled in step [5].
|
||
> NOTE: Because the priority of the PendSV exception is the lowest of all interrupts, it is actually triggered only after all nested interrupts exit. The PendSV exception is then entered through the efficient **tail-chaining** process, which eliminates the restoring and re-entering the interrupt context.
|
||
</li>
|
||
</ul>
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qk-qk_asm QK Assembly Code for ARM Cortex-M
|
||
The QK port to ARM Cortex-M requires coding the PendSV and NMI exceptions in assembly. This ARM Cortex-M-specific code, as well as QK initialization (@c QK_init()) is located in the file <span class="img file">ports/arm-cm/qk/gnu/qk_port.s</span>
|
||
|
||
@note
|
||
The single assembly module `qk_port.s` contains common code for all Cortex-M variants (Architecture v6M and v7M) as well as options with and without the VFP. The CPU variants are distinguished by conditional compilation, when necessary.
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_qk_port-asm_init QK_init() Implementation
|
||
|
||
<b>Listing: QK_init function in qk_port.s assembly file</b>
|
||
@code{c}
|
||
/*****************************************************************************
|
||
* The QK_init() function sets the priority of PendSV to 0xFF (lowest urgency).
|
||
* For Cortex-M3/4/7, it also sets priorities of all other exceptions and IRQs
|
||
* to the safe value. All this is performed in a nestable critical section.
|
||
*****************************************************************************/
|
||
.section .text.QK_init
|
||
.global QK_init
|
||
.type QK_init, %function
|
||
|
||
[1] QK_init:
|
||
|
||
[2] MRS r0,PRIMASK /* store the state of the PRIMASK in r0 */
|
||
[3] MOV r12,r0 /* r12 := PRIMASK */
|
||
[4] CPSID i /* PRIMASK := 1 */
|
||
|
||
[5] .if __ARM_ARCH == 6 /* Cortex-M0/M0+/M1 (v6-M, v6S-M)? */
|
||
|
||
[6] LDR r3,=0xE000ED18 /* System Handler Priority Register */
|
||
[7] LDR r2,[r3,#8] /* r2 := SYSPRI3 */
|
||
[8] MOVS r0,#0xFF
|
||
[9] LSLS r0,r0,#16
|
||
[10] ORRS r2,r1
|
||
[11] STR r2,[r3,#8] /* SYSPRI3 := r2, PendSV <- 0xFF */
|
||
|
||
[12] .else /* M3/M4/M7 */
|
||
/* NOTE:
|
||
* On Cortex-M3/M4/.., this QK port disables interrupts by means of the
|
||
* BASEPRI register. However, this method cannot disable interrupt
|
||
* priority zero, which is the default for all interrupts out of reset.
|
||
* The following code changes the SysTick priority and all IRQ priorities
|
||
* to the safe value QF_BASEPRI, wich the QF critical section can disable.
|
||
* This avoids breaching of the QF critical sections in case the
|
||
* application programmer forgets to explicitly set priorities of all
|
||
* "kernel aware" interrupts.
|
||
*/
|
||
|
||
/* set all priority bytes to QF_BASEPRI in r1 */
|
||
[13] MOVS r1,#QF_BASEPRI
|
||
LSLS r1,r1,#8
|
||
ORRS r1,r1,#QF_BASEPRI
|
||
LSLS r1,r1,#8
|
||
ORRS r1,r1,#QF_BASEPRI
|
||
LSLS r1,r1,#8
|
||
ORRS r1,r1,#QF_BASEPRI
|
||
|
||
LDR r3,=0xE000ED18 /* System Handler Priority Register */
|
||
LDR r2,[r3] /* r2 := SYSPRI1 */
|
||
ORRS r2,r1 /* r2 |= "all values to QF_BASEPRI" */
|
||
[14] STR r2,[r3] /* SYSPRI1 |= r2, Use-fault/Bus-fault/Mem-fault*/
|
||
|
||
LDR r2,[r3,#4] /* r2 := SYSPRI2 */
|
||
ORRS r2,r1 /* r2 |= "all values to QF_BASEPRI" */
|
||
[16] STR r2,[r3,#4] /* SYSPRI2 := r2, SVCall */
|
||
|
||
LDR r2,[r3,#8] /* r2 := SYSPRI3 */
|
||
ORRS r1,r1,#(0xFF << 16) /* r1 |= 0xFF for PendSV */
|
||
ORRS r2,r1
|
||
[17] STR r2,[r3,#8] /* SYSPRI3 |= r2, SysTick/PendSV/Debug */
|
||
|
||
/* set again all prioriy bytes to QF_BASEPRI in r1 */
|
||
[18] MOVS r1,#QF_BASEPRI
|
||
LSLS r1,r1,#8
|
||
ORRS r1,r1,#QF_BASEPRI
|
||
LSLS r1,r1,#8
|
||
ORRS r1,r1,#QF_BASEPRI
|
||
LSLS r1,r1,#8
|
||
ORRS r1,r1,#QF_BASEPRI
|
||
|
||
[19] LDR r3,=0xE000E004 /* Interrupt Controller Type Register */
|
||
LDR r3,[r3] /* r3 := INTLINESUM */
|
||
LSLS r3,r3,#3
|
||
[20] ADDS r3,r3,#8 /* r3 == number of NVIC_PRIO registers */
|
||
|
||
/* loop over all implemented NVIC_PRIO registers for IRQs... */
|
||
QK_init_irq:
|
||
SUBS r3,r3,#1
|
||
LDR r2,=0xE000E400 /* NVIC_PRI0 register */
|
||
[21] STR r1,[r2,r3,LSL #2] /* NVIC_PRI0[r3] := r1 */
|
||
CMP r3,#0
|
||
BNE QK_init_irq
|
||
|
||
.endif /* M3/M4/M7 */
|
||
|
||
[22] MOV r0,r12 /* r0 := original PRIMASK */
|
||
[23] MSR PRIMASK,r0 /* PRIMASK := r0 */
|
||
[24] BX lr
|
||
.size QK_init, . - QK_init
|
||
@endcode
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">1</span>
|
||
The QK_init() function initializes the exception priorities of PendSV and NMI as well as interrupt priorities of all IRQs available in a given MCU (for Cortex-M3/4/7).
|
||
</li>
|
||
<li><span class="tag">2</span>
|
||
The PRIMASK register is retreived into r0.
|
||
</li>
|
||
<li><span class="tag">3</span>
|
||
The PRIMASK value is stored in the high-register r12.
|
||
</li>
|
||
<li><span class="tag">4</span>
|
||
Interrupts are disabled by setting the PRIMASK.
|
||
</li>
|
||
|
||
<li><span class="tag">5</span>
|
||
For the ARMv6-M architecture...
|
||
</li>
|
||
<li><span class="tag">6</span>
|
||
The address of the NVIC System Handler Priority Register 0 is loaded into r3
|
||
</li>
|
||
<li><span class="tag">7</span>
|
||
The contents of the NVIC System Handler Priority Register 2 (note the offset of 8) is loaded into r2.
|
||
</li>
|
||
<li><span class="tag">8-9</span>
|
||
The mask value of 0xFF0000 is synthesized in r2.
|
||
</li>
|
||
<li><span class="tag">10</span>
|
||
The mask is then applied to set the priority byte PRI_14 to 0xFF without changing other priority bytes in this register.
|
||
</li>
|
||
<li><span class="tag">11</span>
|
||
The contents of r2 is stored in the NVIC System Handler Priority Register 2 (note the offset of 8).
|
||
</li>
|
||
|
||
<li><span class="tag">12</span>
|
||
For the ARMv7-M architecture...
|
||
</li>
|
||
<li><span class="tag">13</span>
|
||
The priority value QF_BASEPRI is all bytes of the register r1.
|
||
</li>
|
||
<li><span class="tag">14</span>
|
||
The exception priorities for User-Fault, Bus-Fault, and Mem-Fault are set to the value QF_BASEPRI.
|
||
</li>
|
||
<li><span class="tag">16</span>
|
||
The exception priority for SVCCall is set to the value QF_BASEPRI.
|
||
</li>
|
||
<li><span class="tag">17</span>
|
||
The exception priority for SysTick, PendSV, and Debug is set to the value QF_BASEPRI.
|
||
</li>
|
||
<li><span class="tag">18</span>
|
||
The priority value QF_BASEPRI is all bytes of the register r1 again.
|
||
</li>
|
||
<li><span class="tag">19</span>
|
||
The value of Interrupt Controller Type Register is loaded into r3
|
||
</li>
|
||
<li><span class="tag">20</span>
|
||
The number of implemented IRQs is loaded into r3
|
||
</li>
|
||
<li><span class="tag">21</span>
|
||
The interrupt priority of all implemented IRQs is set to the safe value QF_BASEPRI in a loop.
|
||
</li>
|
||
|
||
<li><span class="tag">22</span>
|
||
The original value of PRIMASK is restored form the high-register r12
|
||
</li>
|
||
<li><span class="tag">23</span>
|
||
The PRIMASK register is restored to its original value
|
||
</li>
|
||
<li><span class="tag">14</span>
|
||
The function QK_init returns to the caller.
|
||
</li>
|
||
</ul>
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_qk_port-asm_pendsv PendSV_Handler() Implementation
|
||
|
||
<b>Listing: PendSV_Handler function in qk_port.s assembly file</b>
|
||
@code{c}
|
||
.section .text.PendSV_Handler
|
||
.global PendSV_Handler /* CMSIS-compliant exception name */
|
||
.type PendSV_Handler, %function
|
||
|
||
[1] PendSV_Handler:
|
||
/* Prepare some constants in registers before entering critical section */
|
||
[2] LDR r3,=0xE000ED04 /* Interrupt Control and State Register */
|
||
[3] MOVS r1,#1
|
||
[4] LSLS r1,r1,#27 /* r0 := (1 << 27) (UNPENDSVSET bit) */
|
||
|
||
|
||
/* <<<<<<<<<<<<<<<<<<<<<<< CRITICAL SECTION BEGIN <<<<<<<<<<<<<<<<<<<<< */
|
||
.if __ARM_ARCH == 6 /* Cortex-M0/M0+/M1 (v6-M, v6S-M)? */
|
||
[6] CPSID i /* disable interrupts (set PRIMASK) */
|
||
.else /* M3/M4/M7 */
|
||
.ifdef __FPU_PRESENT /* if VFP available... */
|
||
[7] PUSH {r0,lr} /* ... push EXC_RETURN plus stack-aligner */
|
||
.endif /* VFP */
|
||
[8] MOVS r0,#QF_BASEPRI
|
||
[9] CPSID i /* selectively disable interrutps with BASEPRI */
|
||
[10] MSR BASEPRI,r0 /* apply the workaround the Cortex-M7 erraturm */
|
||
[11] CPSIE i /* 837070, see ARM-EPM-064408. */
|
||
.endif /* M3/M4/M7 */
|
||
|
||
/* The PendSV exception handler can be preempted by an interrupt,
|
||
* which might pend PendSV exception again. The following write to
|
||
* ICSR[27] un-pends any such spurious instance of PendSV.
|
||
*/
|
||
[12] STR r1,[r3] /* ICSR[27] := 1 (unpend PendSV) */
|
||
|
||
/* The QK activator must be called in a Thread mode, while this code
|
||
* executes in the Handler mode of the PendSV exception. The switch
|
||
* to the Thread mode is accomplished by returning from PendSV using
|
||
* a fabricated exception stack frame, where the return address is
|
||
* QK_activate_().
|
||
*
|
||
* NOTE: the QK activator is called with interrupts DISABLED and also
|
||
* returns with interrupts DISABLED.
|
||
*/
|
||
[13] LSRS r3,r1,#3 /* r3 := (r1 >> 3), set the T bit (new xpsr) */
|
||
[14] LDR r2,=QK_activate_ /* address of QK_activate_ */
|
||
[15] SUBS r2,r2,#1 /* align Thumb-address at halfword (new pc) */
|
||
[16] LDR r1,=Thread_ret /* return address after the call (new lr) */
|
||
|
||
[17] SUB sp,sp,#8*4 /* reserve space for exception stack frame */
|
||
[18] ADD r0,sp,#5*4 /* r0 := 5 registers below the top of stack */
|
||
[19] STM r0!,{r1-r3} /* save xpsr,pc,lr */
|
||
|
||
[20] MOVS r0,#6
|
||
[21] MVNS r0,r0 /* r0 := ~6 == 0xFFFFFFF9 */
|
||
[22] BX r0 /* exception-return to the QK activator */
|
||
.size PendSV_Handler, . - PendSV_Handler
|
||
|
||
/*
|
||
* Thread_ret is a helper function executed when the QXK activator returns.
|
||
*
|
||
* NOTE: Thread_ret does not execute in the PendSV context!
|
||
* NOTE: Thread_ret executes entirely with interrupts DISABLED.
|
||
*/
|
||
.section .text.Thread_ret
|
||
.type Thread_ret, %function
|
||
|
||
[23] Thread_ret:
|
||
/* After the QK activator returns, we need to resume the preempted
|
||
* thread. However, this must be accomplished by a return-from-exception,
|
||
* while we are still in the thread context. The switch to the exception
|
||
* context is accomplished by triggering the NMI exception.
|
||
*/
|
||
|
||
/* before triggering the NMI exception, make sure that the
|
||
* VFP stack frame will NOT be used...
|
||
*/
|
||
.ifdef __FPU_PRESENT /* if VFP available... */
|
||
[24] MRS r0,CONTROL /* r0 := CONTROL */
|
||
[25] BICS r0,r0,#4 /* r0 := r0 & ~4 (FPCA bit) */
|
||
[26] MSR CONTROL,r0 /* CONTROL := r0 (clear CONTROL[2] FPCA bit) */
|
||
[27] ISB /* ISB after MSR CONTROL (ARM AN321,Sect.4.16)*/
|
||
.endif /* VFP available */
|
||
|
||
/* trigger NMI to return to preempted thread...
|
||
* NOTE: The NMI exception is triggered with nterrupts DISABLED
|
||
*/
|
||
[28] LDR r0,=0xE000ED04 /* Interrupt Control and State Register */
|
||
[29] LSLS r1,r1,#31 /* r1 := (1 << 31) (NMI bit) */
|
||
[30] MOVS r1,#1
|
||
[31] STR r1,[r0] /* ICSR[31] := 1 (pend NMI) */
|
||
[32] B . /* wait for preemption by NMI */
|
||
.size Thread_ret, . - Thread_ret
|
||
@endcode
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">1</span>
|
||
`PendSV_Handler` is a CMSIS-complinat name of the PendSV exception handler. The `PendSV_Handler` exception is always entered via tail-chaining from the last nested interrupt.
|
||
</li>
|
||
<li><span class="tag">2-4</span>
|
||
Before interrupts are disabled, the following constants are loaded into registers: address of ICSR into r3 and (1<<27) into r1.
|
||
</li>
|
||
<li>For the ARMv6-M architecture (Cortex-M0/M0+)...
|
||
</li>
|
||
<li><span class="tag">6</span>
|
||
Interrupts are globally disabled by setting PRIMASK (see Section 3)
|
||
</li>
|
||
<li>Otherwise, for the ARMv7-M architecture (Cortex-M3/4/7) and when the `_FPU_PRESENT` macro is defined...
|
||
> NOTE: The symbol `__FPU_PRESENT` must be defined on the command-line to the GNU-ARM assembler if you intend to use the FPU on the Cortex-M4F/M7 cores.
|
||
</li>
|
||
<li><span class="tag">7</span>
|
||
The lr register (EXC_RETURN) is pushed to the stack along with r0, to keep the stack aligned at 8-byte boundary.
|
||
> NOTE: In the presence of the FPU (Cortex-M4F/M7), the EXC_RETURN[4] bit carries the information about the stack frame format used, whereas EXC_RETURN[4] ==0 means that the stack contains room for the S0-S15 and FPSCR registers in addition to the usual R0-R3,R12,LR,PC,xPSR registers. This information must be preserved, in order to properly return from the exception at the end.
|
||
</li>
|
||
<li><span class="tag">8</span>
|
||
For the ARMv7-M architecture (Cortex-M3/M4), interrupts are selectively disabled by setting the BASEPRI register.
|
||
> NOTE: The value moved to BASEPRI must be identical to the QF_BASEPRI macro defined in `qf_port.h`.
|
||
</li>
|
||
<li><span class="tag">9</span>
|
||
Before setting the BASEPRI register, interrupts are disabled with the PRIMASK register, which is the recommended workaround for the Cortex-M7 r0p1 hardware bug, as described in the ARM Ltd. [@ref ARM-EPM-064408], Erratum 837070.
|
||
</li>
|
||
<li><span class="tag">10</span>
|
||
The BASEPRI register is set to the @c QF_BASEPRI value.
|
||
</li>
|
||
<li><span class="tag">11</span>
|
||
After setting the BASEPRI register, interrupts are re-anabed with the PRIMASK register, which is the recommended workaround for the Cortex-M7 r0p1 hardware bug, as described in the ARM Ltd. [@ref ARM-EPM-064408], Erratum 837070.
|
||
</li>
|
||
<li><span class="tag">12</span>
|
||
The PendSV exception is **explicitly** un-pended.
|
||
> NOTE: The PendSV exception handler can be preempted by an interrupt, which might pend PendSV exception again. This would trigger PendSV incorrectly again immediately after calling QK activator.
|
||
</li>
|
||
|
||
<li>The following code [13-22] fabricates an exception stack frame, to perform an exception-return to the QK activator without destroying the original exception stack frame of the PendSV exception. This is necessary to preserve the context of the preempted code.
|
||
</li>
|
||
<li><span class="tag">13</span>
|
||
The value (1 << 24) is synthesized in r3 from the value (1 << 27) already available in r1. This value is going to be stacked and later restored to xPSR register (only the T bit set).
|
||
</li>
|
||
<li><span class="tag">14</span>
|
||
The address of the QK activator function @c QK_activate_ is loaded into r2. This will be pushed to the stack as the PC register value.
|
||
</li>
|
||
<li><span class="tag">15</span>
|
||
The address of the QK activator function @c QK_activate_ in r2 is adjusted to be half-word aligned instead of being an odd THUMB address.
|
||
> NOTE: This is necessary, because the value will be loaded directly to the PC, which cannot accept odd values.
|
||
</li>
|
||
<li><span class="tag">16</span>
|
||
The address of the Thread_ret function is loaded into r1. This will be pushed to the stack as the lr register value.
|
||
> NOTE: The address of the @c Thread_ret label must be a THUMB address, that is, the least-significant bit of this address must be set (this address must be odd number). This is essential for the correct return of the QK scheduler with setting the THUMB bit in the PSR. Without the LS-bit set, the ARM Cortex-M CPU will clear the T bit in the PSR and cause the Hard Fault. The GNU-ARM assembler/linker will synthesize the correct THUMB address of the svc_ret label only if this label is declared with the `.type Thread_ret , %function` attribute (see step [23]).
|
||
</li>
|
||
<li><span class="tag">16</span>
|
||
The stack pointer is adjusted to leave room for 8 registers.
|
||
</li>
|
||
<li><span class="tag">18</span>
|
||
The top of stack, adjusted by 5 registers, (r0, r1, r2, r3, and r12) is stored to r0.
|
||
</li>
|
||
<li><span class="tag">19</span>
|
||
The values of xpsr, pc, and lr prepared in r3, r2, and r1, respectively, are pushed on the top of stack (now in r0). This operation completes the synthesis of the exception stack frame. After this step the stack looks as follows:
|
||
<pre>
|
||
Hi memory
|
||
(optionally S0-S15, FPSCR), if EXC_RETURN[4]==0
|
||
xPSR
|
||
pc (interrupt return address)
|
||
lr
|
||
r12
|
||
r3
|
||
r2
|
||
r1
|
||
r0
|
||
EXC_RETURN (pushed in step [7] if FPU is present)
|
||
old SP --> "aligner" (pushed in step [7] if FPU is present)
|
||
xPSR == 0x01000000
|
||
PC == QK_activate_
|
||
lr == Thread_ret
|
||
r12 don't care
|
||
r3 don't care
|
||
r2 don't care
|
||
r1 don't care
|
||
SP --> r0 don't care
|
||
Low memory
|
||
</pre>
|
||
</li>
|
||
<li><span class="tag">20-21</span>
|
||
The special exception-return value 0xFFFFFFF9 is synthesized in r0 (two instructions are used to make the code compatible with Cortex-M0, which has no barrel shifter).
|
||
> NOTE: the r0 register is used instead of lr because the Cortex-M0 instruction set cannot manipulate the higher-registers (r9-r15).
|
||
> NOTE: The exception-return value is consistent with the synthesized stack-frame with the lr[4] bit set to 1, which means that the FPU registers are not included in this stack frame.
|
||
</li>
|
||
|
||
<li><span class="tag">22</span>
|
||
PendSV exception returns using the special value of the r0 register of 0xFFFFFFF9 (return to Privileged Thread mode using the Main Stack pointer). The synthesized stack frame causes actually a function call to QK_sched_ function in C.
|
||
> NOTE: The return from the PendSV exception just executed switches the ARM Cortex-M core to the Privileged Thread mode. The QK_sched_ function internally re-enables interrupts before launching any thread, so the threads always run in the Thread mode with interrupts enabled and can be preempted by interrupts of any priority.
|
||
> NOTE: In the presence of the FPU, the exception-return to the QK scheduler does not change any of the FPU status bit, such as CONTROL.FPCA or LSPACT.
|
||
</li>
|
||
|
||
<li><span class="tag">23</span>
|
||
The @c Thread_ret function is the place, where the QK activator `QK_activate_()` returns to, because this return address is pushed to the stack in step [16]. Please note that the address of the @c Thread_ret label must be a THUMB address.
|
||
</li>
|
||
<li><span class="tag">24-27</span>
|
||
If the FPU is present, the read-modify-write code clears the CONTROL[2] bit [2]. This bit, called CONTROL.FPCA (Floating Point Active), would cause generating the FPU-type stack frame, which you want to avoid in this case (because the NMI exception will certainly not use the FPU).
|
||
> NOTE: Clearing the CONTROL.FPCA bit occurs with interrupts disabled, so it is protected from a context switch.
|
||
</li>
|
||
<li><span class="tag">28-31</span>
|
||
The asynchronous NMI exception is triggered by setting ICSR[31]. The job of this exception is to put the CPU into the exception mode and correctly return to the thread level.
|
||
</li>
|
||
<li><span class="tag">32</span>
|
||
This endless loop should not be reached, because the NMI exception should preempt the code immediately after step [31]
|
||
</li>
|
||
</ul>
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_qk_port-asm_nmi NMI_Handler() Implementation
|
||
|
||
<b>Listing: NMI_Handler function in qk_port.s assembly file</b>
|
||
@code{c}
|
||
.section .text.NMI_Handler
|
||
.global NMI_Handler
|
||
.type NMI_Handler, %function
|
||
|
||
[1] NMI_Handler:
|
||
[2] ADD sp,sp,#(8*4) /* remove one 8-register exception frame */
|
||
|
||
.if __ARM_ARCH == 6 /* Cortex-M0/M0+/M1 (v6-M, v6S-M)? */
|
||
[3] CPSIE i /* enable interrupts (clear PRIMASK) */
|
||
[4] BX lr /* return to the preempted thread */
|
||
.else /* M3/M4/M7 */
|
||
[5] MOVS r0,#0
|
||
[6] MSR BASEPRI,r0 /* enable interrupts (clear BASEPRI) */
|
||
.ifdef __FPU_PRESENT /* if VFP available... */
|
||
[7] POP {r0,pc} /* pop stack "aligner" and EXC_RETURN to PC */
|
||
.else /* no VFP */
|
||
[8] BX lr /* return to the preempted thread */
|
||
.endif /* VFP available */
|
||
.endif /* M3/M4/M7 */
|
||
.size NMI_Handler, . - NMI_Handler
|
||
@endcode
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">1</span>
|
||
The @c NMI_Handler is the CMSIS-compliant name of the NMI exception handler. This exception is triggered after returning from the QK activator in step [31] of the previous listing. The job of NMI is to discard its own stack frame and cause the exception-return to the original preempted thread context. The stack contents just after entering NMI is shown below:
|
||
<pre>
|
||
Hi memory
|
||
(optionally S0-S15, FPSCR), if EXC_RETURN[4]==0
|
||
xPSR
|
||
pc (interrupt return address)
|
||
lr
|
||
r12
|
||
r3
|
||
r2
|
||
r1
|
||
r0
|
||
old SP --> EXC_RETURN (pushed in PendSV [7] if FPU is present)
|
||
"aligner" (pushed in PendSV [7] if FPU is present)
|
||
xPSR don't care
|
||
PC don't care
|
||
lr don't care
|
||
r12 don't care
|
||
r3 don't care
|
||
r2 don't care
|
||
r1 don't care
|
||
SP --> r0 don't care
|
||
Low memory
|
||
</pre>
|
||
</li>
|
||
<li><span class="tag">2</span>
|
||
The stack pointer is adjusted to un-stack the 8 registers of the interrupt stack frame corresponding to the NMI exception itself. This moves the stack pointer from the “old SP” to “SP” in the picture above, which "uncovers" the original exception stack frame left by the PendSV exception.
|
||
</li>
|
||
<li><span class="tag">3</span>
|
||
For ARMv6-M, interrupts are enabled by clearing the PRIMASK.
|
||
</li>
|
||
<li><span class="tag">4</span>
|
||
For ARMv6-M, The NMI exception returns to the preempted thread using the standard EXC_RETURN, which is in lr.
|
||
</li>
|
||
<li><span class="tag">5-6</span>
|
||
For the ARMv7-M, interrupts are enabled by writing 0 into the BASEPRI register.
|
||
</li>
|
||
<li><span class="tag">7</span>
|
||
If the FPU is used, the EXC_RETURN and the “stack aligner” saved in PendSV step [7] are popped from the stack into r0 and pc, respectively. Updating the pc causes the return from PendSV.
|
||
</li>
|
||
<li><span class="tag">8</span>
|
||
Otherwise, NMI returns to the preempted thread using the standard EXC_RETURN, which is in lr.
|
||
</li>
|
||
</ul>
|
||
|
||
@anchor qk_stack-detail
|
||
@image html qk_stack-detail.gif "Detailed stack allocation in QK for ARM Cortex-M"
|
||
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qk-isr Writing ISRs for QK
|
||
The ARM Cortex-M CPU is designed to use regular C functions as exception and interrupt service routines (ISRs).
|
||
|
||
@note
|
||
The ARM EABI (Embedded Application Binary Interface) requires the stack be 8-byte aligned, whereas some compilers guarantee only 4-byte alignment. For that reason, some compilers (e.g., GNU-ARM) provide a way to designate ISR functions as interrupts. For example, the GNU-ARM compiler provides the `__attribute__((__interrupt__))` designation that will guarantee the 8-byte stack alignment.
|
||
|
||
|
||
Typically, ISRs are application-specific (with the main purpose to produce events for active objects). Therefore, ISRs are not part of the generic QP port, but rather part of the BSP (Board Support Package).
|
||
|
||
The following listing shows an example of the SysTick_Handler() ISR (from the DPP example application). This ISR calls the QF_TICK_X() macro to perform QF time-event management.
|
||
|
||
@anchor arm-cm_qk-isr-code
|
||
<b>Listing: An ISR header for QK</b>
|
||
@code{c}
|
||
void SysTick_Handler(void) __attribute__((__interrupt__));
|
||
void SysTick_Handler(void) {
|
||
~ ~ ~
|
||
[1] QK_ISR_ENTRY(); /* inform QK about entering an ISR */
|
||
~ ~ ~
|
||
QF_TICK_X(0U, &l_SysTick_Handler); /* process all armed time events */
|
||
~ ~ ~
|
||
[2] QK_ISR_EXIT(); /* inform QK about exiting an ISR */
|
||
}
|
||
@endcode
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">1</span>
|
||
Every ISR for QK must call @c QK_ISR_ENTRY() before calling any QP API
|
||
</li>
|
||
<li><span class="tag">2</span>
|
||
Every ISR for QK must call @c QK_ISR_EXIT() right before exiting to let the QK kernel schedule an asynchronous preemption, if necessary.
|
||
</li>
|
||
</ul>
|
||
|
||
@note
|
||
The QK port to ARM Cortex-M complies with the requirement of the ARM-EABI to preserve stack pointer alignment at **8-byte boundary**. Also, all QP examples for ARM Cortex-M comply with the CMSIS naming convention for all exception handlers and IRQ handlers.
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qk-fpu Using the FPU in the QK Port (Cortex-M4F/M7)
|
||
If you have the Cortex-M4F CPU and your application uses the hardware FPU, it should be enabled because it is turned off out of reset. The CMSIS-compliant way of turning the FPU on looks as follows:
|
||
|
||
@verbatim
|
||
SCB->CPACR |= (0xFU << 20);
|
||
@endverbatim
|
||
|
||
@note
|
||
The FPU must be enabled before executing any floating point instruction. An attempt to execute a floating point instruction will fault if the FPU is not enabled.
|
||
|
||
Depending on wheter or not you use the FPU in your ISRs, the “Vanilla” QP port allows you to configure the FPU in various ways, as described in the following sub-sections.
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_qk-fpu_1thread FPU used in ONE thread only and not in any ISR
|
||
If you use the FPU only at a single thread (active object) and none of your ISRs use the FPU, you can setup the FPU not to use the automatic state preservation and not to use the lazy stacking feature as follows:
|
||
|
||
@verbatim
|
||
FPU->FPCCR &= ~((1U << FPU_FPCCR_ASPEN_Pos) | (1U << FPU_FPCCR_LSPEN_Pos));
|
||
@endverbatim
|
||
|
||
With this setting, the Cortex-M4F processor handles the ISRs in the exact-same way as Cortex-M0-M3, that is, only the standard interrupt frame with R0-R3,R12,LR,PC,xPSR is used. This scheme is the fastest and incurs no additional CPU cycles to save and restore the FPU registers.
|
||
|
||
@note
|
||
This FPU setting will lead to FPU errors, if more than one thread or any of the ISRs indeed start to use the FPU
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_qk-fpu_nthreadd FPU used in more than one thread only or the ISR
|
||
If you use the FPU in more than one of the threads (active objects) or in any of your ISRs, you should setup the FPU to use the automatic state preservation and the lazy stacking feature as follows:
|
||
|
||
FPU->FPCCR |= (1U << FPU_FPCCR_ASPEN_Pos) | (1U << FPU_FPCCR_LSPEN_Pos);
|
||
|
||
This is actually the default setting of the hardware FPU and is recommended for the QK port, because it is safer in view of code evolution. Future changes to the application can easily introduce FPU use in multiple active objects, which would be unsafe if the FPU context was not preserved automatically.
|
||
|
||
@note
|
||
As described in the ARM Application Note "Cortex-M4(F) Lazy Stacking and Context Switching" [@ref ARM-AN298], the FPU automatic state saving requires more stack plus additional CPU time to save the FPU registers, but only when the FPU is actually used.
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qk-idle QK Idle Processing Customization in QK_onIdle()
|
||
QK can very easily detect the situation when no events are available, in which case QK calls the `QK_onIdle()` callback. You can use `QK_onIdle()` to suspended the CPU to save power, if your CPU supports such a power-saving mode. Please note that `QK_onIdle()` is called repetitively from an endless loop, which is the QK idle-thread. The `QK_onIdle()` callback is called with interrupts **enabled** (which is in contrast to the @ref arm-cm_qv-idle "QV_onIdle() callback" used in the non-preemptive configuration).
|
||
|
||
The THUMB-2 instruction set used exclusively in ARM Cortex-M provides a special instruction WFI (Wait-for-Interrupt) for stopping the CPU clock, as described in the “ARMv7-M Reference Manual” [ARM 06a]. The following listing shows the `QK_onIdle()` callback that puts ARM Cortex-M into a low-power mode.
|
||
|
||
@anchor arm-cm_qk_onidle-code
|
||
<b>Listing: QV_onIdle() for ARM Cortex-M</b>
|
||
@code{c}
|
||
|
||
[1] void QK_onIdle(void) {
|
||
~ ~ ~
|
||
[2] #if defined NDEBUG
|
||
/* Put the CPU and peripherals to the low-power mode.
|
||
* you might need to customize the clock management for your application,
|
||
* see the datasheet for your particular Cortex-M3 MCU.
|
||
*/
|
||
[3] __WFI(); /* Wait-For-Interrupt */
|
||
#endif
|
||
}
|
||
@endcode
|
||
|
||
<ul class="tag">
|
||
<li><span class="tag">1</span>
|
||
The preemptive QK kernel calls the `QK_onIdle()` callback with interrupts enabled.
|
||
<li>
|
||
<li><span class="tag">2</span>
|
||
The sleep mode is used only in the non-debug configuration, because sleep mode stops CPU clock, which can interfere with debugging.
|
||
<li>
|
||
<li><span class="tag">3</span>
|
||
The `WFI` instruction is generated using inline assembly.
|
||
<li>
|
||
</ul>
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qk-testing Testing QK Preemption Scenarios
|
||
The `bsp.c` file included in the <span class="img folder">examples/arm-cm/dpp_ek-tm4c123gxl/qk</span> directory contains special instrumentation (an ISR designed for testing) for convenient testing of @ref arm-cm_qk-arm-cm-fig "various preemption scenarios in QK".
|
||
|
||
The technique described in this section will allow you to trigger an interrupt at any machine instruction and observe the preemption it causes. The interrupt used for the testing purposes is the GPIOA interrupt (INTID == 0). The ISR for this interrupt is shown below:
|
||
|
||
@code{c}
|
||
void GPIOPortA_IRQHandler(void) {
|
||
QK_ISR_ENTRY(); /* inform QK about entering an ISR */
|
||
QACTIVE_POST(AO_Table, Q_NEW(QEvt, MAX_PUB_SIG), /* for testing... */
|
||
&l_GPIOPortA_IRQHandler);
|
||
QK_ISR_EXIT(); /* inform QK about exiting an ISR */
|
||
}
|
||
@endcode
|
||
|
||
|
||
`GPIOPortA_IRQHandler()`, as all interrupts in the system, invokes the macros QK_ISR_ENTRY() and QK_ISR_EXIT(), and also posts an event to the Table active object, which has higher priority than any of the Philo active object.
|
||
|
||
The figure below hows how to trigger the GPIOA interrupt from the CCS debugger. From the debugger you need to first open the register window and select NVIC registers from the drop-down list (see right-bottom corner of Figure 6).You scroll to the NVIC_SW_TRIG register, which denotes the Software Trigger Interrupt Register in the NVIC. This write-only register is useful for software-triggering various interrupts by writing various masks to it. To trigger the GPIOA interrupt you need to write 0x00 to the NVIC_SW_TRIG by clicking on this field, entering the value, and pressing the Enter key.
|
||
|
||
@image html arm-cm_qk_test-ccs.png "Triggering the GPIOA interrupt from Eclipse debugger"
|
||
|
||
The general testing strategy is to break into the application at an interesting place for preemption, set breakpoints to verify which path through the code is taken, and trigger the GPIO interrupt. Next, you need to free-run the code (don’t use single stepping) so that the NVIC can perform prioritization. You observe the order in which the breakpoints are hit. This procedure will become clearer after a few examples.
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_qk-test-isr Interrupt Nesting Test
|
||
The first interesting test is verifying the correct tail-chaining to the PendSV exception after the interrupt nesting occurs, as shown in @ref arm-cm_qk-synch-fig "Synchronous Preemption in QK". To test this scenario, you place a breakpoint inside the `GPIOPortA_IRQHandler()` and also inside the `SysTick_Handler()` ISR. When the breakpoint is hit, you remove the original breakpoint and place another breakpoint at the very next machine instruction (use the Disassembly window) and also another breakpoint on the first instruction of the `QK_PendSV` handler. Next you trigger the PIOINT0 interrupt per the instructions given in the previous section. You hit the Run button.
|
||
|
||
The pass criteria of this test are as follows:
|
||
|
||
1. The first breakpoint hit is the one inside the `GPIOPortA_IRQHandler()` function, which means that GPIO ISR preempted the SysTick ISR.
|
||
|
||
2. The second breakpoint hit is the one in the `SysTick_Handler()`, which means that the SysTick ISR continues after the PIOINT0 ISR completes.
|
||
|
||
3. The last breakpoint hit is the one in `PendSV_Handler()` exception handler, which means that the PendSV exception is tail-chained only after all interrupts are processed. You need to remove all breakpoints before proceeding to the next test.
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_qk-test-thread Thread Preemption Test
|
||
The next interesting test is verifying that threads can preempt each other. You set a breakpoint anywhere in the Philosopher state machine code. You run the application until the breakpoint is hit. After this happens, you remove the original breakpoint and place another breakpoint at the very next machine instruction (use the Disassembly window). You also place a breakpoint inside the `GPIOPortA_IRQHandler()` interrupt handler and on the first instruction of the `PendSV_Handler()` handler. Next you trigger the GPIOA interrupt per the instructions given in the previous section. You hit the Run button.
|
||
|
||
The pass criteria of this test are as follows:
|
||
|
||
1. The first breakpoint hit is the one inside the `GPIOPortA_IRQHandler()` function, which means that GPIO ISR preempted the Philo thread.
|
||
|
||
2. The second breakpoint hit is the one in `PendSV_Handler()` exception handler, which means that the PendSV exception is activated before the control returns to the preempted Philosopher thread.
|
||
|
||
3. After hitting the breakpoint in `PendSV_Handler()`, you single step into `QK_activate_()`. You verify that the activator invokes a state handler from the Table state machine. This proves that the Table thread preempts the Philo thread.
|
||
|
||
4. After this you free-run the application and verify that the next breakpoint hit is the one inside the Philosopher state machine. This validates that the preempted thread continues executing only after the preempting thread (the Table state machine) completes.
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_qk-test-fpu Testing the FPU (Cortex-M4F/M7)
|
||
In order to test the FPU, the Board Support Package (BSP) for the Cortex-M4F EK-TM4C123GXL board uses the FPU in the following contexts:
|
||
|
||
- In the idle loop via the `QK_onIdle()` callback (QP priority 0)
|
||
|
||
- In the thread level via the `BSP_random()` function called from all five Philo active objects (QP priorities 1-5).
|
||
|
||
- In the thread level via the `BSP_displayPhiloStat()` function called from the Table active object (QP priorty 6)
|
||
|
||
- In the ISR level via the `SysTick_Handler()` ISR (priority above all threads)
|
||
|
||
To test the FPU, you could step through the code in the debugger and verify that the expected FPU-type exception stack frame is used and that the FPU registers are saved and restored by the “lazy stacking feature” when the FPU is actually used.
|
||
|
||
Next, you can selectively comment out the FPU code at various levels of priority and verify that the QK context switching works as expected with both types of exception stak frames (with and without the FPU).
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-cm_qk-test-other Other Tests
|
||
Other interesting tests that you can perform include changing priority of the GPIOA interrupt to be lower than the priority of SysTick to verify that the PendSV is still activated only after all interrupts complete.
|
||
|
||
In yet another test you could post an event to Philosopher active object rather than Table active object from the `GPIOPortA_IRQHandler()` function to verify that the QK activator will not preempt the Philosopher thread by itself. Rather the next event will be queued and the Philosopher thread will process the queued event only after completing the current event processing.
|
||
|
||
|
||
@next{arm-cm_qxk}
|
||
*/
|
||
/*##########################################################################*/
|
||
/*! @page arm-cm_qxk Preemptive "Dual-Mode" QXK Kernel
|
||
|
||
@tableofcontents
|
||
|
||
<p>This section describes how to use QP on ARM Cortex-M with the preemptive, blocking QXK real-time kernel, which combines the lightweight non-blocking ("basic") threads of QK with traditional blocking ("extended") threads found in conventional RTOS kernels. QXK provides also typical services of a conventional blocking RTOS, such as blocking time-delays and semaphores.
|
||
</p>
|
||
|
||
QXK has been designed specifically for mixing event-driven active objects with traditional blocking code, such as commercial middleware (TCP/IP stacks, UDP stacks, embedded file systems, etc.) or legacy software.
|
||
|
||
@note
|
||
If you are currently using QP on top of a conventional 3rd-party RTOS, consider moving your application to the QXK kernel. QXK provides traditional blocking threads and services, such as semaphores, so you can use traditional blocking code. But QXK is much more tightly integrated with QP than any third-party RTOS, which saves you code space and additional licensing fees.
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
@section arm-cm_qxk-synopsis Synopsis of the QXK Port on ARM Cortex-M
|
||
|
||
<p>The preemptive, blocking QXK kernel works on ARM Cortex-M as follows:
|
||
</p>
|
||
|
||
1. The ARM Cortex-M processor executes application code in the Privileged Thread mode, which is exactly the mode entered out of reset. The exceptions (including all interrupts) are always processed in the Privileged Handler mode.
|
||
|
||
2. QXK uses the Process Stack Pointer (PSP) for handling threads (both active object threads and "naked" blocking-threads) and the Main Stack Pointer (MSP) for handling interrupts and exceptions (such as the PendSV exception).
|
||
|
||
3. The QXK port uses the @c PendSV (exception number 14) to perform thread-to-thread context switch. The application code (your code) must initialize the Interrupt Vector Table with the addresses of the @c PendSV_Handler exception handler. Additionally, the interrupt table must be initialized with the @c SysTick_Handler exception handler.
|
||
@n
|
||
> NOTE: QXK uses only the CMSIS-compliant exception and interrupt names, such as @c PendSV_Handler, @c SysTick_Handler, etc.@n
|
||
> NOTE: The QXK port specifically does **not** use the SVC exception (Supervisor Call). This makes the QXK ports compatible with various "hypervisors" (such as mbed uVisor or Nordic SoftDevice), which use the SVC exception.
|
||
|
||
4. The application code (your code) must call the function QXK_init() to set up the stack of the *idle thread* of the QXK kernel before calling QF_run().
|
||
@n
|
||
> NOTE: Right before starting multithreading, the QXK kernel re-uses the main C-stack as the Main Stack (for interrupts and exceptions), and assigns the provided idle stack to the internal idle thread.
|
||
|
||
5. You need to explicitly **assign priorities of the all interrupts** used in your application, as described in @ref arm-cm_int.
|
||
|
||
6. It is strongly recommended that you do not assign the lowest NVIC priority (0xFF) to any interrupt in your application, because it is used by the PendSV handler. For example, with 3 bits of priority implemented in the NVIC, this leaves the following 7 priority levels for you (listed from the lowest to the highest urgency): 0xC0, 0xA0, 0x80, 0x60, 0x40, 0x20, and 0x00 (the highest priority).
|
||
@n
|
||
> NOTE: The prioritization of interrupts, including the PendSV exception, is performed entirely by the NVIC. Because the PendSV has the lowest priority in the system, the NVIC tail-chains to the PendSV exception only after exiting the last nested interrupt.
|
||
|
||
7. ISRs are written as regular C functions, but they need to call QXK_ISR_ENTRY() before using any QF services, and they must call QXK_ISR_EXIT() after using any of the QF services.
|
||
|
||
8. ARM Cortex-M enters interrupt context without disabling interrupts. Generally, you should not disable interrupts inside your ISRs. In particular, the QF services (such as QF_PUBLISH(), QF_TICK_X(), and QACTIVE_POST()) should be called with interrupts enabled, to avoid nesting of critical sections.
|
||
@n
|
||
> NOTE: If you don't wish an interrupt to be preempted by another interrupt, you can always prioritize that interrupt in the NVIC to a higher or equal level as other interrupts (use a lower numerical value of priority).
|
||
|
||
9. In compliance with the ARM Application Procedure Call Standard (AAPCS), the QXK kernel always preserves the 8-byte alignment of the stack.
|
||
|
||
|
||
<div class="separate"></div>
|
||
@subsection arm-com_qxk_vfp Using the VFP
|
||
If you have the Cortex-M4F/M7 CPU and your application is compiled with the VFP present, the QXK kernel will enable the VFP along with the VFP automatic state preservation and lazy stacking features. This will case any thread that uses the VFP, to automatically use the Cortex-M exception stack frame with VFP (with 18 VFP registers S0-S15 plus VFP status and stack "aligner"). Also, the QXK context switch will add to this the rest of the VFP registers (S16-S31).
|
||
|
||
@note
|
||
A QXK thread (both an active object thread and a "naked" blocking thread) that uses the VFP will use 136 more bytes of its private stack space than a thread that does not use the VFP. Also, a thread that uses the VFP will take longer to perform the context switch.
|
||
|
||
*/
|