QP/C 6.9.0
Cooperative QV Kernel

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. (NOTE: Long RTC steps can be often broken into shorter pieces by means of the "Reminder" state pattern [Reminder])

Remarks
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 /ports/arm-cm/qv/.

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).

  1. 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).

The qep_port.h Header File

The QEP header file for the ARM Cortex-M port is located in /ports/arm-cm/qv/gnu/qep_port.h. The following shows the qep_port.h header file for ARM Cortex-M/GNU. The GNU-ARM compiler is a standard C99 compiler, so it simply includes the <stdint.h> header file that defines the platform-specific exact-with integer types.

Listing: The qep_port.h header file for ARM Cortex-M

#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 */
Remarks
If you use a pre-C99 compiler (e.g., a C89 compiler), you can provide the stdint.h and stdbool.h header files yourself, or you can define the standard integer types and Boolean types directly in the qep_port.h header file.

The qf_port.h Header File

The QF header file for the ARM Cortex-M port is located in /ports/arm-cm/qv/gnu/qf_port.h. This file specifies the interrupt disabling policy (QF critical section) as well as the configuration constants for QF (see Chapter 8 in [PSiCC2]).

Note
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 toolchain. Other toolchains use slightly different conditional compilation macros to select the Cortex-M variants, but implement the same policies.

Listing: The qf_port.h header file for ARM Cortex-M

/* 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 (unconditional interrupt disabling) */
[6] /*#define QF_CRIT_STAT_TYPE not defined */
[7] #define QF_CRIT_ENTRY(dummy) QF_INT_DISABLE()
[8] #define QF_CRIT_EXIT(dummy) QF_INT_ENABLE()
/* CMSIS threshold for "QF-aware" interrupts, see NOTE2 and NOTE5 */
[9] #define QF_AWARE_ISR_CMSIS_PRI 0
/* hand-optimized LOG2 in assembly for Cortex-M0/M0+/M1(v6-M, v6S-M) */
[10] #define QF_LOG2(n_) QF_qlog2((n_))
[11] #else /* Cortex-M3/M4/M7 */
/* Cortex-M3/M4/M7 alternative interrupt disabling with PRIMASK */
[12] #define QF_PRIMASK_DISABLE() __asm volatile ("cpsid i")
[13] #define QF_PRIMASK_ENABLE() __asm volatile ("cpsie i")
/* Cortex-M3/M4/M7 interrupt disabling policy, see NOTE3 and NOTE4 */
[14] #define QF_INT_DISABLE() __asm volatile (\
"cpsid i\n" "msr BASEPRI,%0\n" "cpsie i" :: "r" (QF_BASEPRI) : )
[15] #define QF_INT_ENABLE() __asm volatile (\
"msr BASEPRI,%0" :: "r" (0) : )
/* QF critical section entry/exit (unconditional interrupt disabling) */
[16] /*#define QF_CRIT_STAT_TYPE not defined */
[17] #define QF_CRIT_ENTRY(dummy) QF_INT_DISABLE()
[18] #define QF_CRIT_EXIT(dummy) QF_INT_ENABLE()
/* BASEPRI threshold for "QF-aware" interrupts, see NOTE3 */
[19] #define QF_BASEPRI 0x3F
/* CMSIS threshold for "QF-aware" interrupts, see NOTE5 */
[20] #define QF_AWARE_ISR_CMSIS_PRI (QF_BASEPRI >> (8 - __NVIC_PRIO_BITS))
/* Cortex-M3/M4/M7 provide the CLZ instruction for fast LOG2 */
[21] #define QF_LOG2(n_) ((uint_fast8_t)(32U - __builtin_clz(n_)))
#endif
[22] #define QF_CRIT_EXIT_NOP() __asm volatile ("isb")
#include "qep_port.h" /* QEP port */
#if (__ARM_ARCH == 6) /* Cortex-M0/M0+/M1(v6-M, v6S-M)? */
/* hand-optimized quick LOG2 in assembly */
[23] uint_fast8_t QF_qlog2(uint32_t x);
#endif /* Cortex-M0/M0+/M1(v6-M, v6S-M) */
#include "qv_port.h" /* QV port cooperative kernel port */
#include "qf.h" /* QF platform-independent public interface */
  • 1 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 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.

  • 2 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.
  • 3 As described in the previous Section, the interrupt disabling policy for the ARMv6-M architecture (Cortex-M0/M0+) is different than the policy for the ARMv7-M. In GNU-ARM, 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).

    NOTE: The __ARM_ARCH macro is specific to the GNU-ARM compiler. Other compilers for ARM Cortex-M provide different macros to detect the CPU type.

  • 4-5 For the ARMv6-M architecture, the interrupt disabling policy uses the PRIMASK register to disable interrupts globally. The QF_INT_DISABLE() macro resolves in this case to the inline assembly instruction "CPSD i", which sets the PRIMASK. The QF_INT_ENABLE() macro resolves to the inline assembly instruction "CPSE i", which clears the PRIMASK.
  • 6 The QF_CRIT_STAT_TYPE is NOT defined, meaning that the critical section uses the simple policy of "unconditional interrupt disablin".

    NOTE: The "unconditional interrupt disabling" policy precludes nesting of critical sections, but this is not needed for ARM Cortex-M, because this CPU never disables interrupts, even when handling exceptions/interrupts.

  • 7 The QF_CRIT_ENTRY() enters a critical section. Interrupts are disabled by setting the PRIMASK register.
  • 8 The QF_CRIT_EXIT() macro leaves the critical section. Interrupts are unconditionally re-enabled by clearing the PRIMASK register.
  • 9 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.
  • 10 The QF_LOG2() macro is defined as a call to the function QF_qlog2() ("quick log-base-2 logarithm"). This function is coded in hand-optimized assembly, which always takes only 14 CPU cycles to execute (see also label [23]).

    NOTE: ARM Cortex-M0/M0+ does NOT implement the CLZ instruction. Therefore the log-base-2 calculation cannot be accelerated in hardware, as it is for ARM Cortex-M3 and higher.

  • 11 For the ARMv7-M (Cortex-M3/M4/M4F) architecture...
  • 12 The QF_PRIMASK_DISABLE() macro resolves to the inline assembly instruction CPSD i, which sets the PRIMASK.
  • 13 The QF_PRIMASK_ENABLE() macro resolves to the inline assembly instruction CPSE i, which clears the PRIMASK.
  • 14 Interrupts are disabled by setting the BASEPRI register to the value defined in the QF_BASEPRI macro (see label [19]). This setting of the BASEPRI instruction msr BASEPRI,... is surrounded by setting and clearing the PRIMASK register, as a workaround a hardware problem in ARM Cortex-M7 core r0p1:

    NOTE: The selective disabling of "QF-aware" interrupts with the BASEPRI register has a problem on ARM Cortex-M7 core r0p1 (see [ARM-AT610-611], Erratum 837070). The workaround recommended by ARM is to surround MSR BASEPRI,... with the CPSID i/CPSIE i pair, which is implemented in the QF_INT_DISABLE() macro. This workaround works also for Cortex-M3/M4 cores.

  • 15 The QF_INT_ENABLE() macro sets the BASEPRI register to zero, which disables BASEPRI interrupt masking.

    NOTE: this method can never disable interrupt of priority 0 (highest).

  • 16 The QF_CRIT_STAT_TYPE is NOT defined, meaning that the critical section uses the simple policy of "unconditional interrupt disabling".

    NOTE: The "unconditional interrupt disabling" policy precludes nesting of critical sections, but this is not needed for ARM Cortex-M, because this CPU never disables interrupts, even when handling exceptions/interrupts.

  • 17 The QF_CRIT_ENTRY() enters a critical section. Interrupts are disabled with the macro QF_INT_DISABLE() defined at label [12].
  • 18 The QF_CRIT_EXIT() macro leaves the critical section. Interrupts are unconditionally re-enabled with the macro QF_INT_ENABLE() defined at label [13].
  • 19 The 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 Assigning Interrupt Priorities.
  • 20 For the ARMv7-M architecture, the QF_AWARE_ISR_CMSIS_PRI priority level suitable for the CMSIS function NVIC_SetPriority() is determined by the QF_BASEPRI value.
  • 21 The macro QF_LOG2() is defined to take advantage of the CLZ instruction (Count Leading Zeroes), which is available in the ARMv7-M architecture.

    NOTE: The __builtin_cls() intrinsic function is specific to the GNU-ARM compiler. Other compilers for ARM Cortex-M use different function names for this intrinsic function.

  • 22 The macro QF_CRIT_EXIT_NOP() provides the protection against merging two critical sections occurring back-to-back in the QP code.
  • 23 For ARMv6 architecture, the prototype of the quick, hand-optimized log-base-2 function is provided (see also label [10]).

The qv_port.h Header File

The QV header file for the ARM Cortex-M port is located in /ports/arm-cm/qv/gnu/qv_port.h. 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.

Listing: The qv_port.h header file for ARM Cortex-M

#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/M7 */
[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 */
  • 1 For the ARMv6-M architecture, the macro QV_CPU_SLEEP() 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.
  • 2 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/M7 cores cannot be woken up by any interrupt blocked by the BASEPRI register.
  • 2 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.

The qv_port.c Implementation File

The QV implementation file for the ARM Cortex-M port is located in /ports/arm-cm/qv/gnu/qf_port.c. This file defines the function QV_init(), which for the ARMv7-M architecture sets the interrupt priorities of all IRQs to the safe value QF_BASEPRI.

Listing: The qv_port.c header file for ARM Cortex-M

#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) {
/* 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 */
  • 1 For the ARMv7-M architecture (Cortex-M3/M4/M7)...
  • 2 The exception priorities for User-Fault, Bus-Fault, and Mem-Fault are set to the value QF_BASEPRI.
  • 3 The exception priority for SVCCall is set to the value QF_BASEPRI.
  • 4 The exception priority for SysTick, PendSV, and Debug is set to the value QF_BASEPRI.
  • 5 The number of implemented IRQs is read from the SCnSCB_ICTR register
  • 6 The interrupt priority of all implemented IRQs is set to the safe value QF_BASEPRI in a loop.

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.

Listing: An ISR header for QV

void SysTick_Handler(void) __attribute__((__interrupt__));
void SysTick_Handler(void) {
~ ~ ~
QF_TICK_X(0U, &l_SysTick_Handler); /* process all armed time events */
}
Note
The QP port to ARM Cortex-M complies with the CMSIS standard, which dictates the names of all exception handlers and IRQ handlers.

Using the FPU in the QV Port (Cortex-M4F/M7)

If you have the Cortex-M4/M7 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:

    SCB->CPACR |= (0xFU << 20);
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 whether or not you use the FPU in your ISRs, the QV port allows you to configure the FPU in various ways, as described in the following sub-sections.

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:

    FPU->FPCCR &= ~((1U << FPU_FPCCR_ASPEN_Pos) | (1U << FPU_FPCCR_LSPEN_Pos));

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

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:

    FPU->FPCCR |= (1U << FPU_FPCCR_ASPEN_Pos) | (1U << FPU_FPCCR_LSPEN_Pos);

This will enable the lazy stacking feature of the Cortex-M4F/M7 processor [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" [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.

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.

Listing: QV_onIdle() for ARM Cortex-M

[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
}
  • 1 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.
  • 2 The sleep mode is used only in the non-debug configuration, because sleep mode stops CPU clock, which can interfere with debugging.
  • 3 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.
  • 4 When a sleep mode is not used, the QV_onIdle() callback simply re-enables interrupts.

Next: Preemptive Non-Blocking QK Kernel

QF_LOG2
uint_fast8_t QF_LOG2(QPSetBits x)
Definition: qf_act.c:156
stdbool.h
Boolean type and constansts. WG14/N843 C99 Standard, Section 7.16.
QF_CRIT_ENTRY
#define QF_CRIT_ENTRY(stat_)
Definition: qk/qf_port.h:52
QV_CPU_SLEEP
#define QV_CPU_SLEEP()
Macro to put the CPU to sleep safely in the cooperative QV kernel (inside QV_onIdle()).
Definition: qv_port.h:53
uint_fast8_t
unsigned int uint_fast8_t
fast at-least 8-bit unsigned int
Definition: 16bit/stdint.h:36
QV_onIdle
void QV_onIdle(void)
QV idle callback (customized in BSPs)
QF_MAX_ACTIVE
#define QF_MAX_ACTIVE
The maximum number of active objects in the application.
Definition: qxk/qf_port.h:58
QF_INT_ENABLE
#define QF_INT_ENABLE()
Definition: qk/qf_port.h:45
QF_CRIT_EXIT
#define QF_CRIT_EXIT(stat_)
Definition: qk/qf_port.h:53
qf.h
QF/C platform-independent public interface.
qv_port.h
QV/C port example for a generic C compiler.
qep.h
Public QEP/C interface.
QF_INT_DISABLE
#define QF_INT_DISABLE()
Definition: qk/qf_port.h:44
QF_TICK_X
#define QF_TICK_X(tickRate_, sender_)
Invoke the system clock tick processing QF_tickX_().
Definition: qf.h:675
QF_MAX_TICK_RATE
#define QF_MAX_TICK_RATE
Definition: qxk/qf_port.h:74
QF_CRIT_EXIT_NOP
#define QF_CRIT_EXIT_NOP()
No-operation for exiting a critical section.
Definition: qf.h:853
uint32_t
unsigned long int uint32_t
exact-width 32-bit unsigned int
Definition: 16bit/stdint.h:31