SuperTinyKernel™ RTOS 1.05.3
Lightweight, high-performance, deterministic, bare-metal C++ RTOS for resource-constrained embedded systems. MIT Open Source License.
Loading...
Searching...
No Matches
stk_arch_arm-cortex-m (5).cpp
Go to the documentation of this file.
1/*
2 * SuperTinyKernel™ (STK): Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems.
3 *
4 * Source: https://github.com/SuperTinyKernel-RTOS
5 *
6 * Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com>. All Rights Reserved.
7 * License: MIT License, see LICENSE for a full text.
8 */
9
10// note: If missing, this header must be customized (get it in the root of the source folder) and
11// copied to the /include folder manually.
12#include "stk_config.h"
13
14#ifdef _STK_ARCH_ARM_CORTEX_M
15
16//#define STK_CORTEX_M_TRUSTZONE
17
18#ifdef STK_CORTEX_M_TRUSTZONE
19#include <arm_cmse.h>
20#endif
21
22#include "stk.h"
23#include "stk_arch.h"
25
26using namespace stk;
27
29#ifndef __CORTEX_M
30#error Expecting __CORTEX_M with value corresponding to Cortex-M model (0, 3, 4, ...)!
31#endif
32
34#if !defined(SysTick)
35 #error "SysTick peripheral definition is missing"
36#endif
37
39#if (__CORTEX_M > 1U) && STK_TICKLESS_USE_ARM_DWT && !defined(DWT)
40 #error "DWT peripheral definition is missing"
41#endif
42
44#define STK_CORTEX_M_FPU ((__FPU_PRESENT == 1U) && (__FPU_USED == 1U))
45
47#define STK_CORTEX_M_MANAGE_LR (__CORTEX_M >= 3U)
48
49// Non-Secure (and pre-ARMv8-M) EXC_RETURN values -- S bit (bit 6) = 0.
50#define STK_CORTEX_M_EXC_RETURN_HANDLR_MSP 0xFFFFFFF1U // Handler mode, MSP stack (Non-Secure)
51#define STK_CORTEX_M_EXC_RETURN_THREAD_MSP 0xFFFFFFF9U // Thread mode, MSP stack (Non-Secure)
52#define STK_CORTEX_M_EXC_RETURN_THREAD_PSP 0xFFFFFFFDU // Thread mode, PSP stack (Non-Secure)
53
54// ARMv8-M TrustZone Secure EXC_RETURN values -- S bit (bit 6) = 1.
55// These are only meaningful when STK_CORTEX_M_TRUSTZONE is defined on M23/M33+.
56// Reference: ARMv8-M Architecture Reference Manual, section B1.5.8.
57#define STK_CORTEX_M_EXC_RETURN_S_HANDLR_MSP 0xFFFFFFE1U // Secure, Handler mode, MSP
58#define STK_CORTEX_M_EXC_RETURN_S_THREAD_MSP 0xFFFFFFE9U // Secure, Thread mode, MSP
59#define STK_CORTEX_M_EXC_RETURN_S_THREAD_PSP 0xFFFFFFEDU // Secure, Thread mode, PSP
60// Non-Secure return with Non-Secure FPU state saved (used for NS tasks on M33 with FPU).
61#define STK_CORTEX_M_EXC_RETURN_NS_THREAD_PSP 0xFFFFFFBCU // Non-Secure, Thread, PSP, NS-FPU
62
64#define STK_CORTEX_M_EXC_RETURN_S_BIT 0x00000040U
65
66#define STK_CORTEX_M_ISR_PRIORITY_HIGHEST 0
67#define STK_CORTEX_M_ISR_PRIORITY_LOWEST 0xFF
68
70#if STK_CORTEX_M_MANAGE_LR
71 #define STK_CORTEX_M_REGISTER_COUNT 17
72#else
73 #define STK_CORTEX_M_REGISTER_COUNT 16
74#endif
75
77#if defined(STK_CORTEX_M_TRUSTZONE) && (__CORTEX_M >= 33U)
78 #define STK_CORTEX_M_TZ_REGISTER_COUNT 2
79#else
80 #define STK_CORTEX_M_TZ_REGISTER_COUNT 0
81#endif
82
84#define STK_CORTEX_M_TOTAL_REGISTER_COUNT \
85 (STK_CORTEX_M_REGISTER_COUNT + STK_CORTEX_M_TZ_REGISTER_COUNT)
86
88#ifndef STK_SYSTICK_HANDLER
89 #define STK_SYSTICK_HANDLER SysTick_Handler
90#endif
91
93#ifndef STK_PENDSV_HANDLER
94 #define STK_PENDSV_HANDLER PendSV_Handler
95#endif
96
98#ifndef STK_SVC_HANDLER
99 #define STK_SVC_HANDLER SVC_Handler
100#endif
101
103enum ESvcCommandId : uint8_t
104{
105 SVC_START_SCHEDULING = 0,
106 SVC_ENTER_CRITICAL,
107 SVC_EXIT_CRITICAL,
108 //SVC_FORCE_SWITCH
109};
110
126#if defined(STK_CORTEX_M_TRUSTZONE) && (__CORTEX_M >= 33U)
127struct TrustZoneFrame
128{
129 Word PSPLIM;
130 Word PSPLIM_NS;
131};
132#endif
133
137struct ExceptionFrame
138{
139 Word R0;
140 Word R1;
141 Word R2;
142 Word R3;
143 Word R12;
144 Word LR;
145 Word PC;
146 Word PSR;
147};
148
164struct TaskFrame
165{
166#if defined(STK_CORTEX_M_TRUSTZONE) && (__CORTEX_M >= 33U)
167 TrustZoneFrame tz;
168#endif
169#if STK_CORTEX_M_MANAGE_LR
170 Word EXC_RETURN;
171#endif
172 ExceptionFrame exc;
173};
174
175// Shortcuts:
176#define STK_ASM_EXIT_FROM_HANDLER "BX LR" // use in naked exception/ISR handlers
177#define STK_ASM_EXIT_FROM_FUNCTION "BX LR" // use in naked C-callable wrapper functions
178#define STK_ASM_DISABLE_INTERRUPTS "CPSID i"
179#define STK_ASM_ENABLE_INTERRUPTS "CPSIE i"
180
184static __stk_forceinline void HW_StartScheduler()
185{
186 __asm volatile("SVC %0"
187 : /* output: none */
188 : "I"(SVC_START_SCHEDULING)
189 : "memory" /* protect against compiler reordering */ );
190}
191
194#ifdef SVC_FORCE_SWITCH
195static __stk_forceinline void HW_ForceContextSwitch()
196{
197 __asm volatile("SVC %0"
198 : /* output: none */
199 : "I"(SVC_FORCE_SWITCH)
200 : "memory" /* protect against compiler reordering */ );
201}
202#endif
203
222__stk_attr_naked Word HW_SVCEnterCritical()
223{
224 __asm volatile(
225 "SVC %0 \n"
226 STK_ASM_EXIT_FROM_FUNCTION " \n"
227 : /* output: none */
228 : "I"(SVC_ENTER_CRITICAL)
229 : "memory" /* protect against compiler reordering */ );
230}
231
249__stk_attr_naked void HW_SVCExitCritical(Word /*prev*/)
250{
251 __asm volatile(
252 "SVC %0 \n"
253 STK_ASM_EXIT_FROM_FUNCTION " \n"
254 : /* output: none */
255 : "I"(SVC_EXIT_CRITICAL)
256 : "memory" /* protect against compiler reordering */);
257}
258
261static __stk_forceinline void HW_DisableInterrupts()
262{
263#if (defined(__clang__) && defined(__ARMCOMPILER_VERSION)) || defined(__ICCARM__)
264 __asm volatile(STK_ASM_DISABLE_INTERRUPTS ::: "memory");
265#else
266 __disable_irq();
267#endif
268}
269
272static __stk_forceinline void HW_EnableInterrupts()
273{
274#if (defined(__clang__) && defined(__ARMCOMPILER_VERSION)) || defined(__ICCARM__)
275 __asm volatile(STK_ASM_ENABLE_INTERRUPTS ::: "memory");
276#else
277 __enable_irq();
278#endif
279}
280
284static __stk_forceinline bool HW_InterruptsDisabled()
285{
286#if (defined(__clang__) && defined(__ARMCOMPILER_VERSION)) || defined(__ICCARM__)
287 Word primask;
288 __asm volatile("MRS %0, primask" : "=r"(primask));
289 return (primask & 1U);
290#else
291 return (__get_PRIMASK() & 1U);
292#endif
293}
294
298static __stk_forceinline void HW_CriticalSectionStart(uint32_t &SES)
299{
300 SES = __get_PRIMASK();
301 HW_DisableInterrupts();
302
303 // ensure the disable is recognized before subsequent code
304 __DSB();
305 __ISB();
306}
307
311static __stk_forceinline void HW_CriticalSectionEnd(uint32_t SES)
312{
313 // ensure all memory work is finished before re-enabling
314 __DSB();
315
316 __set_PRIMASK(SES);
317
318 // synchronization point: any pending interrupt can be serviced immediately at this boundary
319 __ISB();
320}
321
322#ifdef CONTROL_nPRIV_Msk
335static __stk_forceinline bool HW_SpinLockTryLock(volatile bool &lock)
336{
337 return !__atomic_test_and_set(&lock, __ATOMIC_ACQUIRE);
338}
339
352static __stk_forceinline void HW_SpinLockUnlock(volatile bool &lock)
353{
354 if (!lock)
355 STK_KERNEL_PANIC(KERNEL_PANIC_SPINLOCK_DEADLOCK); // release attempt of unowned lock
356
357 // ensure all data writes (like scheduling metadata) are flushed before the lock is released:
358 // __atomic_clear with __ATOMIC_RELEASE provides the required store-release barrier,
359 // the explicit dmb ishst is retained for toolchains that do not lower __ATOMIC_RELEASE
360 // to a full DMB on ARMv7-M (e.g. older GCC versions with -mcpu=cortex-m4)
361 __asm volatile("dmb ishst" ::: "memory");
362
363 __atomic_clear(&lock, __ATOMIC_RELEASE);
364}
365#elif defined(RP2040_H)
366// Raspberry RP2040 dual-core M0+ implementation, using Hardware Spinlock 0 (SIO base 0xd0000000 + offset)
367#define STK_SIO_SPINLOCK SIO->SPINLOCK31
368
384static __stk_forceinline bool HW_SpinLockTryLock(volatile bool &lock)
385{
386 bool success = (STK_SIO_SPINLOCK == 0 ? false : ((lock) = true, true));
387 __DMB();
388
389 return success;
390}
391
408static __stk_forceinline void HW_SpinLockUnlock(volatile bool &lock)
409{
410 if (!lock)
411 STK_KERNEL_PANIC(KERNEL_PANIC_SPINLOCK_DEADLOCK); // release attempt of unowned lock
412
413 __DMB();
414 (lock) = false;
415 STK_SIO_SPINLOCK = 1; // writing any value releases the hardware lock
416}
417
418#undef STK_SIO_SPINLOCK
419#else // !RP2040_H
420// Standard single-core Cortex-M0 implementation:
421
435static __stk_forceinline bool HW_SpinLockTryLock(volatile bool &lock)
436{
437 uint32_t ses;
438 HW_CriticalSectionStart(ses);
439
440 if (lock)
441 {
442 HW_CriticalSectionEnd(ses);
443 return false;
444 }
445
446 lock = true;
447 __DMB();
448
449 HW_CriticalSectionEnd(ses);
450 return true;
451}
452
464static __stk_forceinline void HW_SpinLockUnlock(volatile bool &lock)
465{
466 if (!lock)
467 STK_KERNEL_PANIC(KERNEL_PANIC_SPINLOCK_DEADLOCK); // release attempt of unowned lock
468
469 __DMB();
470 lock = false;
471}
472#endif // CONTROL_nPRIV_Msk
473
489static __stk_forceinline void HW_SpinLockLock(volatile bool &lock)
490{
491 uint32_t timeout = 0xFFFFFF;
492 while (!HW_SpinLockTryLock(lock))
493 {
494 if (--timeout == 0)
495 {
496 // invariant violated: the lock owner exited without releasing
498 }
500 }
501}
502
527
537struct JmpFrame
538{
539 Word R4, R5, R6, R7, R8, R9, R10, R11;
540 Word SP;
541 Word LR;
542#if STK_CORTEX_M_FPU
543 Word FPSCR;
544#endif
545};
546
562int32_t SaveJmp(JmpFrame &/*f*/)
563{
564 __asm volatile(
565 ".syntax unified \n"
566#if (__CORTEX_M >= 3U)
567 // Cortex-M3/M4/M7: STMIA stores r4-r11 at r0+0 .. r0+28
568 "STMIA r0, {r4-r11} \n" // store r4-r11 at offsets 0-28, no writeback
569 "STR sp, [r0, #32] \n" // SP at offset 32
570 "STR lr, [r0, #36] \n" // LR at offset 36
571#else
572 // Cortex-M0/M0+/M1: Thumb-1 only
573 "STR r4, [r0, #0] \n"
574 "STR r5, [r0, #4] \n"
575 "STR r6, [r0, #8] \n"
576 "STR r7, [r0, #12] \n"
577 "MOV r1, r8 \n"
578 "STR r1, [r0, #16] \n"
579 "MOV r1, r9 \n"
580 "STR r1, [r0, #20] \n"
581 "MOV r1, r10 \n"
582 "STR r1, [r0, #24] \n"
583 "MOV r1, r11 \n"
584 "STR r1, [r0, #28] \n"
585 "MOV r1, sp \n"
586 "STR r1, [r0, #32] \n"
587 "MOV r1, lr \n"
588 "STR r1, [r0, #36] \n"
589#endif
590
591#if STK_CORTEX_M_FPU
592 "VMRS r1, FPSCR \n"
593 "STR r1, [r0, #40] \n"
594#endif
595 "MOVS r0, #0 \n"
596 "BX lr \n");
597}
598
618void RestoreJmp(JmpFrame &/*f*/, int32_t /*val*/)
619{
620 __asm volatile(
621 ".syntax unified \n"
622#if (__CORTEX_M >= 3U)
623 // Cortex-M3/M4/M7: LDMIA loads r4-r11 from offsets 0-28
624 "LDR sp, [r0, #32] \n" // restore SP
625#if STK_CORTEX_M_FPU
626 "LDR r2, [r0, #40] \n" // load saved FPSCR
627 "VMSR FPSCR, r2 \n" // restore rounding mode + flags
628#endif
629 "LDR r2, [r0, #36] \n" // load saved LR into r2
630 "LDMIA r0, {r4-r11} \n" // restore r4-r11, no writeback
631 "MOV r0, r1 \n" // return val
632 "BX r2 \n"
633#else
634 // Cortex-M0/M0+/M1: Thumb-1 only
635 "LDR r2, [r0, #36] \n"
636 "MOV lr, r2 \n"
637 "LDR r2, [r0, #32] \n"
638 "MOV sp, r2 \n"
639 "LDR r2, [r0, #28] \n"
640 "MOV r11, r2 \n"
641 "LDR r2, [r0, #24] \n"
642 "MOV r10, r2 \n"
643 "LDR r2, [r0, #20] \n"
644 "MOV r9, r2 \n"
645 "LDR r2, [r0, #16] \n"
646 "MOV r8, r2 \n"
647 "LDR r4, [r0, #0] \n"
648 "LDR r5, [r0, #4] \n"
649 "LDR r6, [r0, #8] \n"
650 "LDR r7, [r0, #12] \n"
651 "MOV r0, r1 \n" // return val
652 "BX lr \n"
653#endif
654 );
655}
656
657// -----------------------------------------------------------------------------
658
661static __stk_forceinline bool HW_IsHandlerMode()
662{
663 return (__get_IPSR() != 0U);
664}
665
669static __stk_forceinline bool HW_IsPrivilegedThreadMode()
670{
671#ifdef CONTROL_nPRIV_Msk
672 return ((__get_CONTROL() & CONTROL_nPRIV_Msk) == 0U);
673#else
674 return true;
675#endif
676}
677
681static __stk_forceinline Word HW_GetCallerSP()
682{
683 // use SP (R13) directly: __get_PSP() returns 0 in unprivileged thread mode
684 Word sp;
685 __asm volatile("MOV %0, sp" : "=r" (sp));
686 return sp;
687}
688
691static __stk_forceinline void HW_ScheduleContextSwitch()
692{
693 SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;
694}
695
698static __stk_forceinline void HW_ClearFpuState()
699{
700#if STK_CORTEX_M_FPU
701 __set_CONTROL(__get_CONTROL() & ~CONTROL_FPCA_Msk);
702#endif
703}
704
707static __stk_forceinline void HW_EnableFullFpuAccess()
708{
709#if STK_CORTEX_M_FPU
710 // enable FPU CP10/CP11 Secure and Non-secure register access
711 SCB->CPACR |= (0b11 << 20) | (0b11 << 22);
712#endif
713}
714
718static __stk_forceinline uint32_t HW_CoreClockFrequency()
719{
720 return SystemCoreClock;
721}
722
725static __stk_forceinline void HW_SysTickStart(uint32_t period_ticks)
726{
727 uint32_t result = SysTick_Config(static_cast<uint32_t>(ConvertTimeUsToClockCycles(HW_CoreClockFrequency(), period_ticks)));
728 STK_ASSERT(result == 0U);
729 (void)result;
730
731 // QEMU workaround (Launchpad Bug #1872237):
732 // SysTick_Config() writes VAL=0 before setting ENABLE=1. On QEMU,
733 // systick_reload() silently discards VAL writes while ENABLE=0, leaving
734 // the internal tick accumulator stale from the previous period.
735 // Writing VAL=0 here, after ENABLE=1 is already set, forces a correct reload.
736 // This is a no-op on real Cortex-M hardware (spec: ARMv7-M ARM B3.3.1).
737 SysTick->VAL = 0U;
738}
739
742static __stk_forceinline void HW_SysTickStop()
743{
744 SysTick->CTRL = 0U;
745 SCB->ICSR = SCB_ICSR_PENDSTCLR_Msk;
746}
747
750static __stk_forceinline void HW_SysTickDisable()
751{
752 SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
753}
754
758static __stk_forceinline uint32_t HW_SysTickValueAfterDisable()
759{
760 // is required for QEMU which then resets SysTick->VAL and thus we can't calculate elapsed time correctly
761 __DSB();
762
763 // check for a QEMU case and discard elapsed result
764 uint32_t VAL = SysTick->VAL;
765 if (VAL == 0)
766 VAL = SysTick->LOAD;
767
768 return VAL;
769}
770
774static __stk_forceinline uint32_t HW_SysTickGetElapsed(uint32_t VAL)
775{
776 return SysTick->LOAD - VAL;
777}
778
782static __stk_forceinline void HW_SysTickRearm(uint32_t ticks)
783{
784 SysTick->LOAD = ticks - 1U;
785 SysTick->VAL = 0U;
786 SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
787}
788
791static __stk_forceinline void HW_DWTEnableCounter()
792{
793#if defined(CoreDebug)
794 // enable Trace and Debug blocks (DWT, ITM, ETM, TPIU)
795 CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
796 __DSB();
797#endif
798
799 // LAR (Lock Access Register) is mandatory for Cortex-M7 to allow register writes,
800 // it is typically not implemented or deprecated on M0, M3, M4, and M33
801#if (__CORTEX_M == 7U)
802 DWT->LAR = 0xC5ACCE55; // unlock DWT unit using standard CoreSight magic key
803 __DSB();
804#endif
805
806#if defined(DWT)
807 // disable counter briefly to safely reset the value to zero
808 DWT->CTRL &= ~DWT_CTRL_CYCCNTENA_Msk;
809 DWT->CYCCNT = 0;
810
811 // enable
812 DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
813#endif
814}
815
820static __stk_forceinline uint32_t HW_DWTGetCounter()
821{
822#if defined(DWT)
823 return DWT->CYCCNT;
824#else
825 return 0U;
826#endif
827}
828
830static volatile bool s_StkCortexmCsuLock = false;
831
833static struct Context : public PlatformContext
834{
835 Context() : PlatformContext(), m_exit_buf(), m_overrider(nullptr),
837 m_sleep_ticks(0), m_sleep_error(0U),
838 #endif
839 m_csu(0U), m_csu_nesting(0U), m_started(false), m_exiting(false)
840 {}
841
845 ~Context()
846 {}
847
848 void Initialize(IPlatform::IEventHandler *handler, IKernelService *service, Stack *exit_trap,
849 uint32_t resolution_us)
850 {
851 PlatformContext::Initialize(handler, service, exit_trap, resolution_us);
852
853 STK_STATIC_ASSERT_DESC_N(SP, offsetof(Stack, SP) == 0U,
854 "expect Stack::mode member at offset of 0 (first member)");
855 STK_STATIC_ASSERT_DESC_N(mode, offsetof(Stack, mode) == 4U,
856 "expect Stack::mode member at offset of 4 (second member)");
857
858 m_csu = 0U;
859 m_csu_nesting = 0U;
860 m_overrider = nullptr;
861 m_started = false;
862 m_exiting = false;
863
864 #if (__CORTEX_M > 1) && STK_TICKLESS_USE_ARM_DWT
865 HW_DWTEnableCounter();
866 #endif
867
868 #if STK_SEGGER_SYSVIEW
869 SEGGER_SYSVIEW_Init(
870 HW_CoreClockFrequency(),
871 HW_CoreClockFrequency(),
872 nullptr,
873 SendSysDesc);
874 #endif
875 }
876
877 __stk_forceinline void OnTick()
878 {
879 HW_DisableInterrupts();
880
881 #if STK_TICKLESS_IDLE
882 Timeout ticks = m_sleep_ticks;
883 #endif
884
885 if (m_handler->OnTick(m_stack_idle, m_stack_active
887 , ticks
888 #endif
889 ))
890 {
891 #if STK_SEGGER_SYSVIEW
892 SEGGER_SYSVIEW_OnTaskStopExec();
893 if (GetContext().m_stack_active->tid != SYS_TASK_ID_SLEEP)
894 SEGGER_SYSVIEW_OnTaskStartExec(GetContext().m_stack_active->tid);
895 #endif
896
897 HW_ScheduleContextSwitch();
898 }
899
900 // rearm SysTick only if tick period changed
901 #if STK_TICKLESS_IDLE
902 if (ticks != m_sleep_ticks)
903 {
904 ReloadTickPeriod(ticks);
905 m_sleep_ticks = ticks;
906 }
907 #endif
908
909 HW_EnableInterrupts();
910 }
911
912 __stk_forceinline void EnterCriticalSection()
913 {
914 // disable local interrupts first to prevent self-deadlock
915 uint32_t current_ses;
916 HW_CriticalSectionStart(current_ses);
917
918 if (m_csu_nesting == 0U)
919 {
920 // ONLY attempt the global spinlock if we aren't already nested
921 HW_SpinLockLock(s_StkCortexmCsuLock);
922
923 // store the hardware interrupt state to restore later
924 m_csu = current_ses;
925 }
926
927 // increase nesting count within a limit
928 if (++m_csu_nesting > STK_CRITICAL_SECTION_NESTINGS_MAX)
929 {
930 // invariant violated: exceeded max allowed number of recursions
931 STK_KERNEL_PANIC(KERNEL_PANIC_CS_NESTING_OVERFLOW);
932 }
933 }
934
935 __stk_forceinline void ExitCriticalSection()
936 {
937 STK_ASSERT(m_csu_nesting != 0U);
938 --m_csu_nesting;
939
940 if (m_csu_nesting == 0U)
941 {
942 // capture the state before releasing lock
943 uint32_t ses_to_restore = m_csu;
944
945 // release global lock
946 HW_SpinLockUnlock(s_StkCortexmCsuLock);
947
948 // restore hardware interrupts
949 HW_CriticalSectionEnd(ses_to_restore);
950 }
951 }
952
953 // Unprivileged
954 __stk_forceinline void UnprivEnterCriticalSection()
955 {
956 // elevate to privileged/disabled state via SVC
957 Word current_ses = HW_SVCEnterCritical();
958
959 if (m_csu_nesting == 0U)
960 {
961 // ONLY attempt the global spinlock if we aren't already nested
962 HW_SpinLockLock(s_StkCortexmCsuLock);
963
964 // store the hardware interrupt state to restore later
965 m_csu = current_ses;
966 }
967
968 // increase nesting count within a limit
969 if (++m_csu_nesting > STK_CRITICAL_SECTION_NESTINGS_MAX)
970 {
971 // invariant violated: exceeded max allowed number of recursions
972 STK_KERNEL_PANIC(KERNEL_PANIC_CS_NESTING_OVERFLOW);
973 }
974 }
975
976 // Unprivileged
977 __stk_forceinline void UnprivExitCriticalSection()
978 {
979 STK_ASSERT(m_csu_nesting != 0U);
980 --m_csu_nesting;
981
982 if (m_csu_nesting == 0U)
983 {
984 // capture the state before releasing lock
985 Word ses_to_restore = m_csu;
986
987 // release global lock
988 HW_SpinLockUnlock(s_StkCortexmCsuLock);
989
990 // restore hardware interrupts via SVC
991 HW_SVCExitCritical(ses_to_restore);
992 }
993 }
994
995 void Start();
996 void OnStart();
997 void OnStop();
998#if STK_TICKLESS_IDLE
999 void ReloadTickPeriod(Timeout ticks_requested);
1000#endif
1001
1002 typedef IPlatform::IEventOverrider eovrd_t;
1003
1004 JmpFrame m_exit_buf;
1005 eovrd_t *m_overrider;
1006#if STK_TICKLESS_IDLE
1007 Timeout m_sleep_ticks;
1008 uint32_t m_sleep_error;
1009#endif
1010 Word m_csu;
1011 uint8_t m_csu_nesting;
1012 volatile bool m_started;
1013 bool m_exiting;
1014}
1015s_StkPlatformContext[STK_ARCH_CPU_COUNT];
1016
1017// ---------------------------------------------------------------------------
1018// TrustZone TLS helpers (Change ⑤ / Fix B)
1019//
1020// Placed here — after the Context struct is fully defined — so no forward
1021// declaration is needed. GetContext() is a macro (stk_arch_common.h) that
1022// expands to s_StkPlatformContext[cpu_id], which is now visible.
1023//
1024// When STK_CORTEX_M_TRUSTZONE is active on ARMv8-M, r9 is the Non-Secure
1025// base register (SB) in the FDPIC ABI and must not be used as a TLS register
1026// across world transitions. The TLS word is instead kept in Stack::tls so it
1027// is saved and restored automatically on every context switch.
1028//
1029// GetTls() / SetTls() in the header forward to these two C-linkage functions
1030// to avoid a circular include dependency (the header cannot see Context).
1031// ---------------------------------------------------------------------------
1032#if defined(STK_CORTEX_M_TRUSTZONE) && (__CORTEX_M >= 33U)
1033
1034extern "C" stk::Word PlatformArmCortexM_GetTlsFromContext()
1035{
1036 return GetContext().m_stack_active->tls;
1037}
1038
1039extern "C" void PlatformArmCortexM_SetTlsInContext(stk::Word tp)
1040{
1041 GetContext().m_stack_active->tls = tp;
1042}
1043
1044#endif // STK_CORTEX_M_TRUSTZONE && __CORTEX_M >= 33U
1045// ---------------------------------------------------------------------------
1046
1048class HiResClockDWT
1049{
1050 Cycles m_acc;
1051 uint32_t m_prev;
1052
1053public:
1054 HiResClockDWT() : m_acc(0), m_prev(0U)
1055 {
1056 HW_DWTEnableCounter();
1057
1058 m_prev = HW_DWTGetCounter();
1059 m_acc = 0;
1060 }
1061
1062 static HiResClockDWT *GetInstance()
1063 {
1064 // keep declaration function-local to allow compiler stripping it from the binary if
1065 // it is unused by the user code
1066 static HiResClockDWT clock;
1067 return &clock;
1068 }
1069
1070 void Update()
1071 {
1072 const uint32_t current = HW_DWTGetCounter();
1073
1074 // unsigned subtraction handles the wrap-around perfectly
1075 uint32_t delta = current - m_prev;
1076 m_acc += delta;
1077
1078 m_prev = current;
1079 }
1080
1081 Cycles GetCycles()
1082 {
1083 Update();
1084 return m_acc;
1085 }
1086
1087 uint32_t GetFrequency()
1088 {
1089 return HW_CoreClockFrequency();
1090 }
1091};
1092
1094class HiResClockM0
1095{
1096public:
1097 static HiResClockM0 *GetInstance()
1098 {
1099 // keep declaration function-local to allow compiler stripping it from the binary if
1100 // it is unused by the user code
1101 static HiResClockM0 clock;
1102 return &clock;
1103 }
1104
1105 Cycles GetCycles()
1106 {
1107 // On M0, combine the coarse OS ticks with the fine-grained SysTick counter
1108 Cycles cycles = ConvertTimeUsToClockCycles(HW_CoreClockFrequency(), stk::GetTicks() * GetContext().m_tick_resolution);
1109
1110 uint32_t val = SysTick->VAL; // down-counter (cycles remaining in current tick)
1111 uint32_t load = SysTick->LOAD; // current reload value
1112
1113 // total elapsed cycles
1114 return cycles + static_cast<Cycles>(load - val);
1115 }
1116
1117 uint32_t GetFrequency()
1118 {
1119 return HW_CoreClockFrequency();
1120 }
1121};
1122
1123#if (__CORTEX_M >= 3U)
1124 typedef HiResClockDWT HiResClockImpl;
1125#else
1126 typedef HiResClockM0 HiResClockImpl;
1127#endif
1128
1130static volatile EKernelPanicId g_LastPanicId = KERNEL_PANIC_NONE;
1131
1132__stk_attr_noinline // keep out of inlining to preserve stack frame
1133__stk_attr_noreturn // never returns - a trap
1135{
1136 g_LastPanicId = id;
1137
1138 // disable all maskable interrupts: this prevents scheduler from running again and corrupting state further
1139 HW_DisableInterrupts();
1140
1141 // spin forever: with a watchdog active this produces a clean reset, without a watchdog,
1142 // a debugger can attach and inspect 'id'
1143 for (;;)
1144 {
1146 }
1147}
1148
1150{
1151 GetContext().OnTick();
1152}
1153
1154#if STK_TICKLESS_IDLE
1155void Context::ReloadTickPeriod(Timeout ticks_requested)
1156{
1157 uint32_t reload = static_cast<uint32_t>(ConvertTimeUsToClockCycles(HW_CoreClockFrequency(), m_tick_resolution));
1158
1159 // guard against uint32_t overflow in the reload calculation
1160 STK_ASSERT(static_cast<uint64_t>(ticks_requested) * reload <= UINT32_MAX);
1161
1162 // start counting how many CPU cycles further instructions take until SysTick timer is enabled again;
1163 // without DWT we will have tick error of around 80 cycles depending on CPU model and compiler optimization
1164#if (__CORTEX_M > 1) && STK_TICKLESS_USE_ARM_DWT
1165 const uint32_t error = HW_DWTGetCounter();
1166 __stk_compiler_barrier(); // prevent reordering, we measure all cycles of instructions below this point
1167#endif
1168
1169 // pause SysTick
1170 HW_SysTickDisable();
1171
1172 // get already elapsed CPU cycles since SysTick ISR invocation up to SysTick timer stop (see above)
1173 // to account for them for a new period value
1174 const uint32_t elapsed_till_stop = HW_SysTickGetElapsed(HW_SysTickValueAfterDisable());
1175
1176 // OnTick() should not consume more than next period
1177 STK_ASSERT(static_cast<Timeout>(elapsed_till_stop / reload) <= static_cast<Timeout>(ticks_requested));
1178
1179 const uint32_t cpu_ticks_requested = static_cast<uint32_t>(ticks_requested) * reload;
1180
1181 // substract number of cycles elapsed till SysTick stop + error from previous round
1182 uint32_t new_load = cpu_ticks_requested - elapsed_till_stop - m_sleep_error;
1183
1184 // clamp: rearm overhead must never push new_load into underflow
1185 if (new_load > cpu_ticks_requested)
1186 new_load = cpu_ticks_requested;
1187
1188 // reload with elapsed ticks accounted
1189 HW_SysTickRearm(new_load);
1190
1191#if (__CORTEX_M > 1) && STK_TICKLESS_USE_ARM_DWT
1192 // calculate error: subtract cycles consumed by the rearm sequence itself in the next round
1193 m_sleep_error = HW_DWTGetCounter() - error;
1194#endif
1195}
1196#endif
1197
1198extern "C" void STK_SYSTICK_HANDLER()
1199{
1200#if STK_SEGGER_SYSVIEW
1201 SEGGER_SYSVIEW_RecordEnterISR();
1202#endif
1203
1204 Context &ctx = GetContext();
1205
1206#ifdef HAL_MODULE_ENABLED // STM32 HAL
1207 // make sure STM32 HAL gets timing information as it depends on SysTick in delaying procedures
1208#if STK_TICKLESS_IDLE
1209 uwTick += ctx.m_sleep_ticks * ctx.m_tick_resolution;
1210#else
1211 HAL_IncTick();
1212#endif
1213
1214 // STM32 HAL starts SysTick on its initialization that will cause a crash on NULL,
1215 // therefore use additional check if HAL_MODULE_ENABLED is defined
1216 if (ctx.m_started)
1217 {
1218#else
1219 {
1220 // make sure SysTick is enabled by the Kernel::Start(), disable its start anywhere else
1221 STK_ASSERT(ctx.m_started);
1222 STK_ASSERT(ctx.m_handler != nullptr);
1223#endif
1224 ctx.OnTick();
1225 }
1226
1227#if STK_SEGGER_SYSVIEW
1228 SEGGER_SYSVIEW_RecordExitISR();
1229 SEGGER_SYSVIEW_IsStarted();
1230#endif
1231}
1232
1241#define STK_ASM_BLOCK_PRIVILEGE_MODE\
1242 "LDR r1, %[st_active]\n" /* r1 = Stack* (m_stack_active) */ \
1243 "LDR r1, [r1, #4] \n" /* r1 = m_stack_active->mode (offset 4), reuse r1 */ \
1244 "CMP r1, %[priv_val] \n" /* compare with ACCESS_PRIVILEGED */ \
1245 "BEQ 1f \n" /* if equal, privileged mode */\
1246 /* unprivileged mode: set CONTROL.nPRIV = 1 */\
1247 "MRS r0, CONTROL \n"\
1248 "ORR r0, r0, #1 \n"\
1249 "MSR CONTROL, r0 \n"\
1250 "B 2f \n"\
1251 "1: \n"\
1252 /* privileged mode: set CONTROL.nPRIV = 0 */\
1253 "MRS r0, CONTROL \n"\
1254 "BIC r0, r0, #1 \n"\
1255 "MSR CONTROL, r0 \n"\
1256 "2: \n"
1257
1258extern "C" __stk_attr_naked void STK_PENDSV_HANDLER()
1259{
1260 __asm volatile(
1261 ".syntax unified \n"
1262
1263 STK_ASM_DISABLE_INTERRUPTS " \n"
1264
1265 // save stack to inactive (idle) task
1266 "MRS r0, psp \n"
1267
1268#if STK_CORTEX_M_FPU
1269 // save FP registers
1270 "TST LR, #16 \n" /* test LR for 0xffffffe_, e.g. Thread mode with FP data */
1271 "IT EQ \n"
1272 "VSTMDBEQ r0!, {s16-s31} \n" /* store 16 SP registers */
1273#endif
1274
1275 // save general registers
1276#if STK_CORTEX_M_MANAGE_LR
1277 // save r4-r11 and LR
1278 // note: for Cortex-M3 and higher save LR to keep correct Thread state of the task when it is restored
1279 "STMDB r0!, {r4-r11, LR}\n"
1280#else
1281 // note: STMIA is limited to r0-r7 range, therefore save via stack memory
1282 "SUBS r0, r0, #16 \n" /* decrement for r4-r7 */
1283 "STMIA r0!, {r4-r7} \n"
1284 "MOV r4, r8 \n"
1285 "MOV r5, r9 \n"
1286 "MOV r6, r10 \n"
1287 "MOV r7, r11 \n"
1288 "SUBS r0, r0, #32 \n" /* decrement for r8-r11 and pointer reset */
1289 "STMIA r0!, {r4-r7} \n"
1290 "SUBS r0, r0, #16 \n" /* final pointer adjustment */
1291#endif
1292
1293 // store in GetContext().m_stack_idle
1294 "LDR r1, %[st_idle] \n"
1295 "STR r0, [r1] \n" /* store the first member (SP) from r0 */
1296
1297 // ARMv8-M TrustZone: save PSPLIM (Secure) and PSPLIM_NS (Non-Secure) stack
1298 // limit registers into the idle stack frame so overflow detection stays
1299 // correct after the switch. The two words are pushed BELOW the callee-saved
1300 // registers (r0 already points past them), matching TrustZoneFrame layout.
1301 // Reference: ARMv8-M ARM B3.29 / B3.30.
1302#if defined(STK_CORTEX_M_TRUSTZONE) && (__CORTEX_M >= 33U)
1303 "MRS r2, PSPLIM \n" /* Secure PSPLIM */
1304 "MRS r3, PSPLIM_NS \n" /* Non-Secure PSPLIM */
1305 "STMDB r0!, {r2, r3} \n" /* push below saved general regs */
1306 "STR r0, [r1] \n" /* update idle stack SP to include TZ words */
1307#endif
1308
1309 // set privileged/unprivileged mode for the active stack
1310#ifdef CONTROL_nPRIV_Msk
1311 STK_ASM_BLOCK_PRIVILEGE_MODE
1312#endif
1313
1314 // load stack of the active task from GetContext().m_stack_active (note: keep in sync with OnTaskStart)
1315 "LDR r1, %[st_active] \n"
1316 "LDR r0, [r1] \n" /* load the first member of Stack (Stack::SP) into r0 */
1317
1318 // ARMv8-M TrustZone: restore PSPLIM (Secure) and PSPLIM_NS (Non-Secure)
1319 // stack limit registers from the active task frame. The two words were
1320 // pushed beneath the callee-saved registers by the save path above.
1321 // They are popped first so that r0 advances to the callee-saved region.
1322#if defined(STK_CORTEX_M_TRUSTZONE) && (__CORTEX_M >= 33U)
1323 "LDMIA r0!, {r2, r3} \n" /* pop PSPLIM, PSPLIM_NS */
1324 "MSR PSPLIM, r2 \n" /* restore Secure PSPLIM */
1325 "MSR PSPLIM_NS, r3 \n" /* restore Non-Secure PSPLIM */
1326#endif
1327
1328 // restore general registers
1329#if STK_CORTEX_M_MANAGE_LR
1330 // restore r4-r11 and LR
1331 "LDMIA r0!, {r4-r11, LR}\n"
1332#else
1333 // note: LDMIA is limited to r0-r7 range, therefore load via stack memory
1334 "LDMIA r0!, {r4-r7} \n"
1335 "MOV r8, r4 \n"
1336 "MOV r9, r5 \n"
1337 "MOV r10, r6 \n"
1338 "MOV r11, r7 \n"
1339 "LDMIA r0!, {r4-r7} \n"
1340#endif
1341
1342#if STK_CORTEX_M_FPU
1343 // restore FP registers
1344 "TST LR, #16 \n" /* test LR for 0xffffffe_, e.g. Thread mode with FP data */
1345 "IT EQ \n" /* if result is positive */
1346 "VLDMIAEQ r0!, {s16-s31} \n" /* restore FP registers */
1347#endif
1348
1349 "MSR psp, r0 \n" /* restore psp */
1350
1351 STK_ASM_ENABLE_INTERRUPTS " \n"
1352
1353 STK_ASM_EXIT_FROM_HANDLER " \n"
1354
1355 : /* output: none */
1356 : [st_idle] "m" (GetContext().m_stack_idle),
1357 [st_active] "m" (GetContext().m_stack_active),
1358 [priv_val] "i" (ACCESS_PRIVILEGED)
1359 : "r0", "r1" /* scratchpad; r2, r3 additionally used in the TrustZone PSPLIM path */ );
1360}
1361
1362__stk_attr_naked void OnTaskStart()
1363{
1364 // note: HW_DisableInterrupts() must be called prior calling this function
1365
1366 __asm volatile(
1367 ".syntax unified \n"
1368
1369 // set privileged/unprivileged mode for the active stack
1370#ifdef CONTROL_nPRIV_Msk
1371 STK_ASM_BLOCK_PRIVILEGE_MODE
1372#endif
1373
1374 // load stack of the active task from GetContext().m_stack_active (note: keep in sync with OnTaskStart)
1375 "LDR r1, %[st_active] \n"
1376 "LDR r0, [r1] \n" /* load the first member of Stack (Stack::SP) into r0 */
1377
1378 // restore general registers
1379#if STK_CORTEX_M_MANAGE_LR
1380 // restore r4-r11 and LR
1381 "LDMIA r0!, {r4-r11, LR}\n"
1382#else
1383 // note: LDMIA is limited to r0-r7 range, therefore load via stack memory
1384 "LDMIA r0!, {r4-r7} \n"
1385 "MOV r8, r4 \n"
1386 "MOV r9, r5 \n"
1387 "MOV r10, r6 \n"
1388 "MOV r11, r7 \n"
1389 "LDMIA r0!, {r4-r7} \n"
1390#endif
1391
1392#if STK_CORTEX_M_FPU
1393 // restore FP registers
1394 "TST LR, #16 \n" /* test LR for 0xffffffe_, e.g. Thread mode with FP data */
1395 "IT EQ \n" /* if result is positive */
1396 "VLDMIAEQ r0!, {s16-s31} \n" /* restore FP registers */
1397#endif
1398
1399 // restore psp
1400 "MSR psp, r0 \n"
1401
1402#if !STK_CORTEX_M_MANAGE_LR
1403 // M0: set LR to Thread mode, use PSP state and stack
1404 "LDR r0, =%[exc_ret] \n"
1405 "MOV LR, r0 \n"
1406#endif
1407
1408 STK_ASM_ENABLE_INTERRUPTS " \n"
1409
1410 STK_ASM_EXIT_FROM_HANDLER " \n"
1411
1412 : /* output: none */
1413 : [st_active] "m" (GetContext().m_stack_active),
1414 [priv_val] "i" (ACCESS_PRIVILEGED),
1415 [exc_ret] "i" (STK_CORTEX_M_EXC_RETURN_THREAD_PSP)
1416 : "r0", "r1" /* only r0, r1 are used as a scratchpad */ );
1417}
1418
1419void Context::Start()
1420{
1421 m_exiting = false;
1422
1423 // save jump location of the Exit trap
1424 SaveJmp(m_exit_buf);
1425 if (m_exiting)
1426 {
1427 // notify kernel about a full stop
1428 m_handler->OnStop();
1429 return;
1430 }
1431
1432 HW_StartScheduler();
1433}
1434
1435void Context::OnStart()
1436{
1437 // interrupts must be disabled at this point
1438 STK_ASSERT(HW_InterruptsDisabled());
1439
1440 // FPU
1441 HW_EnableFullFpuAccess();
1442
1443 // clear FPU usage status if FPU was used before kernel start
1444 HW_ClearFpuState();
1445
1446 // notify kernel
1447 m_handler->OnStart(m_stack_active);
1448
1449#if STK_TICKLESS_IDLE
1450 // reset sleep ticks if kernel was restarted
1451 m_sleep_ticks = 1;
1452#endif
1453
1454 // start SysTick timer (it is yet can't fire an interrupt due to HW_DisableInterrupts)
1455 HW_SysTickStart(m_tick_resolution);
1456
1457 // set priority
1458 // note: after SysTick_Config because it may change SysTick priority, PendSV and SysTick peripherals
1459 // have equal priority to avoid race
1460 NVIC_SetPriority(PendSV_IRQn, STK_CORTEX_M_ISR_PRIORITY_LOWEST);
1461 NVIC_SetPriority(SysTick_IRQn, STK_CORTEX_M_ISR_PRIORITY_LOWEST);
1462 // set highest priority for SVC interrupts to support critical section for unprivileged tasks
1463#ifdef CONTROL_nPRIV_Msk
1464 NVIC_SetPriority(SVCall_IRQn, STK_CORTEX_M_ISR_PRIORITY_HIGHEST);
1465#endif
1466
1467 m_started = true;
1468}
1469
1470// __stk_attr_used required for Link-Time Optimization (-flto)
1471extern "C" __stk_attr_used void SVC_Handler_Main(Word *svc_args)
1472{
1473 // Word is typedef uintptr_t (stk_common.h) - the only integer type the Standard
1474 // blesses for lossless pointer round-trips (MISRA C++ 5-2-8, CERT INT36-C)
1475 STK_STATIC_ASSERT_DESC_N(PTR, sizeof(Word) == sizeof(void *),
1476 "Word must be uintptr_t width for safe pointer round-trip via frame->PC");
1477
1478 // priority 0 (NMI, HardFault) unaffected: SVC (priority 0 per OnStart()) remains
1479 // reachable so SVC_EXIT_CRITICAL can always unwind
1480 STK_STATIC_ASSERT_DESC_N(NVIC, __NVIC_PRIO_BITS < 32u,
1481 "NVIC priority bit width exceeds safe shift range");
1482
1483 // 'volatile': R0 is written back to stacked memory, compiler must not eliminate the store
1484 volatile ExceptionFrame * const frame = reinterpret_cast<volatile ExceptionFrame *>(svc_args);
1485
1486 // details: https://developer.arm.com/documentation/ka004005/latest
1487 // Thumb SVC encoding: [15:8] = 0xDF, [7:0] = imm8
1488 // ---
1489 // opcode lives two bytes (one Thumb halfword) before the stacked PC:
1490 // do explicit subtraction instead of negative indexing (MISRA C++ 5-0-16),
1491 // uint8_t extracted before ESvcCommandId cast to avoid impl-defined enum conversion (MISRA 5-2-6)
1492 const uint8_t *insn_ptr = hw::WordToPtr<const uint8_t>(frame->PC) - 2;
1493 const ESvcCommandId command = static_cast<ESvcCommandId>(*insn_ptr);
1494
1495 switch (command)
1496 {
1497 case SVC_START_SCHEDULING: {
1498 // disallow any duplicate attempt
1499 STK_ASSERT(!GetContext().m_started);
1500 if (GetContext().m_started)
1501 return;
1502
1503 // make sure interrupts do not interfere, OnStart expects interrupts disabled
1504 HW_DisableInterrupts();
1505
1506 GetContext().OnStart();
1507
1508 // start first task
1509 OnTaskStart();
1510 break; }
1511
1512 /*case SVC_FORCE_SWITCH: {
1513 HW_ScheduleContextSwitch();
1514 break; }*/
1515
1516#ifdef CONTROL_nPRIV_Msk
1517 case SVC_ENTER_CRITICAL: {
1518 const Word saved_basepri = __get_BASEPRI();
1519 __set_BASEPRI(static_cast<uint32_t>(1U) << __NVIC_PRIO_BITS); // mask all configurable-priority interrupts
1520 __DSB(); // BASEPRI write visible to bus before SVC return
1521 __ISB(); // pipeline flush: mask in effect at first caller instruction
1522 frame->R0 = saved_basepri; // return saved value via stacked R0
1523 break; }
1524
1525 case SVC_EXIT_CRITICAL: {
1526 __DSB(); // drain pending stores before widening interrupt window
1527 __set_BASEPRI(frame->R0); // restore saved BASEPRI (passed in R0)
1528 __ISB(); // pending interrupts at restored priority may fire now
1529 break; }
1530#endif // CONTROL_nPRIV_Msk
1531
1532 default: {
1533 // any SVC number not in ESvcCommandId is a defect, panic unconditionally
1535 break; }
1536 }
1537}
1538
1539// details: "How to Write an SVC Function", https://developer.arm.com/documentation/ka004005/latest
1540extern "C" __stk_attr_naked void STK_SVC_HANDLER()
1541{
1542 __asm volatile(
1543 ".syntax unified \n"
1544 ".global SVC_Handler_Main \n"
1545 ".align 2 \n" // ensure the entry point is aligned
1546
1547#ifdef STK_CORTEX_M_TRUSTZONE
1548 // ARMv8-M TrustZone (Cortex-M33 / Mainline)
1549 // EXC_RETURN bit layout (ARMv8-M ARM B1.5.8):
1550 // bit 6 (0x40): 1 = Secure stack, 0 = Non-Secure stack. <-- S-bit
1551 // bit 2 (0x04): 1 = PSP, 0 = MSP.
1552 // We test bit 6 first to select the correct world's stack pointer,
1553 // then bit 2 to choose MSP vs PSP within that world.
1554 #if (__CORTEX_M >= 33U)
1555 // -----------------------------------------------------------------
1556 // Cortex-M33 and later (ARMv8-M Mainline) -- IT/ITE available.
1557 // -----------------------------------------------------------------
1558 "TST LR, #4 \n" // bit 2: 0 = MSP, 1 = PSP
1559 "BNE .use_psp \n"
1560 // --- MSP branch ---
1561 "TST LR, #64 \n" // bit 6 (S-bit): 1 = Secure, 0 = Non-Secure
1562 "ITE NE \n"
1563 "MRSNE r0, MSP \n" // r0 = Secure MSP
1564 "MRSEQ r0, MSP_NS \n" // r0 = Non-Secure MSP
1565 "B .call_main \n"
1566
1567 ".use_psp: \n"
1568 "TST LR, #64 \n" // bit 6 (S-bit): 1 = Secure, 0 = Non-Secure
1569 "ITE NE \n"
1570 "MRSNE r0, PSP \n" // r0 = Secure PSP
1571 "MRSEQ r0, PSP_NS \n" // r0 = Non-Secure PSP
1572 #else
1573 // -----------------------------------------------------------------
1574 // Cortex-M23 (ARMv8-M Baseline) -- no IT instructions, limited ISA.
1575 // Shift bits into the sign position and use BMI (Branch if Minus).
1576 // bit 2 -> sign: LSLS r1, #29 (32 - 3 = 29)
1577 // bit 6 -> sign: LSLS r1, #25 (32 - 7 = 25)
1578 // -----------------------------------------------------------------
1579 "MOV r1, LR \n"
1580 "LSLS r1, r1, #29 \n" // shift bit 2 into sign
1581 "BMI .use_psp_v8m_b \n" // bit 2 set -> PSP branch
1582 // --- MSP branch ---
1583 "MOV r1, LR \n"
1584 "LSLS r1, r1, #25 \n" // shift bit 6 (S-bit) into sign
1585 "BMI .use_msp_s \n" // S=1 -> Secure MSP
1586 "MRS r0, MSP_NS \n" // S=0 -> Non-Secure MSP
1587 "B .call_main \n"
1588
1589 ".use_msp_s: \n"
1590 "MRS r0, MSP \n" // r0 = Secure MSP
1591 "B .call_main \n"
1592
1593 ".use_psp_v8m_b: \n"
1594 "MOV r1, LR \n"
1595 "LSLS r1, r1, #25 \n" // shift bit 6 (S-bit) into sign
1596 "BMI .use_psp_s \n" // S=1 -> Secure PSP
1597 "MRS r0, PSP_NS \n" // S=0 -> Non-Secure PSP
1598 "B .call_main \n"
1599
1600 ".use_psp_s: \n"
1601 "MRS r0, PSP \n" // r0 = Secure PSP
1602 #endif
1603
1604 ".call_main: \n"
1605#elif (__CORTEX_M >= 3U)
1606 // Cortex-M3/M4/M7
1607 "TST LR, #4 \n" // check EXC_RETURN bit 2
1608 "ITE EQ \n"
1609 "MRSEQ r0, MSP \n" // r0 = MSP
1610 "MRSNE r0, PSP \n" // else r0 = PSP
1611#else
1612 // Cortex-M0/M0+ (limited ISA)
1613 "MOV r0, LR \n" // r0 = LR
1614 "LSLS r0, r0, #29 \n" // if (r0 & 4)
1615 "BMI .use_psp_m0 \n" // else
1616 "MRS r0, MSP \n" // r0 = MSP
1617 "B .call_main_m0 \n"
1618
1619 ".use_psp_m0: \n"
1620 "MRS r0, PSP \n" // else r0 = PSP
1621
1622 ".call_main_m0: \n"
1623#endif
1624
1625 // even on Cortex-M3+, a long jump is safer when using LTO, we load address into register to allow far jump (>2KB)
1626 "LDR r1, =SVC_Handler_Main \n"
1627 "BX r1 \n"
1628
1629 ".align 2 \n" // ensure literal pool is aligned
1630 ".pool \n"); // ensure literal pool is reachable
1631}
1632
1633static void OnTaskRun(ITask *task)
1634{
1635 task->Run();
1636}
1637
1638static void OnTaskExit()
1639{
1640 uint32_t cs;
1641 HW_CriticalSectionStart(cs);
1642
1643 GetContext().m_handler->OnTaskExit(GetContext().m_stack_active);
1644
1645 HW_CriticalSectionEnd(cs);
1646
1647 for (;;)
1648 {
1649 __DSB();
1650 __WFI(); // enter standby mode until time slot expires
1651 }
1652}
1653
1654static void OnSchedulerSleep()
1655{
1656 for (;;)
1657 {
1658 #if STK_SEGGER_SYSVIEW
1659 SEGGER_SYSVIEW_OnIdle();
1660 #endif
1661
1662 SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; // disable deep-sleep, go into a WAIT mode (sleep)
1663 __DSB(); // ensure store takes effect (see ARM info)
1664 __WFI(); // enter sleep mode
1665 }
1666}
1667
1668static void OnSchedulerSleepOverride()
1669{
1670 if (!GetContext().m_overrider->OnSleep())
1671 OnSchedulerSleep();
1672}
1673
1674static void OnSchedulerExit()
1675{
1676 __set_CONTROL(0U); // switch to MSP
1677 __set_PSP(0U); // clear PSP (for a clean register state)
1678
1679 // jump back to SaveJmp's return site with m_exiting already set to true
1680 RestoreJmp(GetContext().m_exit_buf, 0);
1681}
1682
1683#if STK_SEGGER_SYSVIEW
1684static void SendSysDesc()
1685{
1686 SEGGER_SYSVIEW_SendSysDesc("SuperTinyKernel (STK)");
1687}
1688#endif
1689
1690void PlatformArmCortexM::Initialize(IEventHandler *event_handler, IKernelService *service, uint32_t resolution_us,
1691 Stack *exit_trap)
1692{
1693 GetContext().Initialize(event_handler, service, exit_trap, resolution_us);
1694}
1695
1697{
1698 GetContext().Start();
1699}
1700
1701bool PlatformArmCortexM::InitStack(EStackType stack_type, Stack *stack, IStackMemory *stack_memory, ITask *user_task)
1702{
1703 // Precondition (TrustZone builds): stack->mode must be set by the caller
1704 // before this function is invoked. On TZ builds the EXC_RETURN selection
1705 // below reads stack->mode to decide between Secure (0xED) and Non-Secure
1706 // (0xBC) EXC_RETURN values. In the Kernel class, KernelTask::Bind() sets
1707 // stack->mode from user_task->GetAccessMode() immediately before calling
1708 // InitStack(), satisfying this requirement.
1709 STK_STATIC_ASSERT_DESC_N(ExceptionFrame, (sizeof(ExceptionFrame) == (8 * sizeof(Word))),
1710 "ExceptionFrame layout must match the ARMv7-M hardware exception frame exactly");
1711 STK_STATIC_ASSERT_DESC_N(TaskFrame, sizeof(TaskFrame) == (8 + STK_CORTEX_M_MANAGE_LR + STK_CORTEX_M_TZ_REGISTER_COUNT) * sizeof(Word),
1712 "TaskFrame size must equal ExceptionFrame plus optional EXC_RETURN word plus optional TrustZone words");
1713
1714 STK_ASSERT(stack_memory->GetStackSize() > STK_CORTEX_M_TOTAL_REGISTER_COUNT);
1715
1716 // initialize stack memory
1717 Word *stack_top = Context::InitStackMemory(stack_memory);
1718
1719 // initialize Stack Pointer (SP)
1720 stack->SP = hw::PtrToWord(stack_top - STK_CORTEX_M_TOTAL_REGISTER_COUNT);
1721
1722 // place the initial task frame flush against the top of the stack:
1723 // TaskFrame::exc (ExceptionFrame) occupies the top 8 words, TaskFrame::EXC_RETURN
1724 // (when present) sits immediately below it, and TrustZoneFrame (when present) sits
1725 // below that.
1726 TaskFrame * const task_frame = reinterpret_cast<TaskFrame *>(stack_top) - 1;
1727
1728 // initialize registers for the user task's first start
1729 switch (stack_type)
1730 {
1731 case STACK_USER_TASK: {
1732 task_frame->exc.PC = hw::PtrToWord(&OnTaskRun) & ~0x1UL;
1733 task_frame->exc.LR = hw::PtrToWord(&OnTaskExit);
1734 task_frame->exc.R0 = hw::PtrToWord(user_task);
1735 break; }
1736
1737 case STACK_SLEEP_TRAP: {
1738 task_frame->exc.PC = hw::PtrToWord(GetContext().m_overrider != nullptr ? &OnSchedulerSleepOverride : &OnSchedulerSleep);
1739 task_frame->exc.LR = STK_STACK_MEMORY_FILLER; // should not attempt to exit
1740 task_frame->exc.R0 = 0U;
1741 break; }
1742
1743 case STACK_EXIT_TRAP: {
1744 task_frame->exc.PC = hw::PtrToWord(&OnSchedulerExit);
1745 task_frame->exc.LR = STK_STACK_MEMORY_FILLER; // should not attempt to exit
1746 task_frame->exc.R0 = 0U;
1747 break; }
1748
1749 default:
1750 return false;
1751 }
1752
1753 // details: "Program counter: Bit [0] is always 0, so instructions are always aligned to halfword boundaries",
1754 // https://developer.arm.com/documentation/ddi0413/c/programmer-s-model/registers/general-purpose-registers
1755 task_frame->exc.PC &= ~0x1UL;
1756
1757 // set T bit of EPSR sub-register to enable execution of instructions
1758 // details: "Special-purpose program status registers (xPSR)": Execution PSR, https://developer.arm.com/documentation/ddi0413/c/programmer-s-model/registers/special-purpose-program-status-registers--xpsr-
1759 task_frame->exc.PSR = static_cast<Word>(1U) << 24;
1760
1761#if STK_CORTEX_M_MANAGE_LR
1762 /*
1763 * Choose the correct EXC_RETURN for the new task.
1764 *
1765 * ARMv8-M TrustZone (Cortex-M33+):
1766 * - Secure tasks must use 0xFFFFFFED (S-bit = 1, Thread, PSP).
1767 * - Non-Secure tasks (and all tasks on plain ARMv7-M) use 0xFFFFFFFD.
1768 *
1769 * The Secure/Non-Secure classification is derived from the stack's
1770 * access mode. ACCESS_PRIVILEGED tasks running in the Secure partition
1771 * are given the Secure EXC_RETURN; all others get the Non-Secure value.
1772 *
1773 * Reference: ARMv8-M Architecture Reference Manual B1.5.8.
1774 */
1775#if defined(STK_CORTEX_M_TRUSTZONE) && (__CORTEX_M >= 33U)
1776 if (stack_type == STACK_USER_TASK && stack->mode == ACCESS_PRIVILEGED)
1777 task_frame->EXC_RETURN = STK_CORTEX_M_EXC_RETURN_S_THREAD_PSP;
1778 else
1779 task_frame->EXC_RETURN = STK_CORTEX_M_EXC_RETURN_NS_THREAD_PSP;
1780#else
1781 task_frame->EXC_RETURN = STK_CORTEX_M_EXC_RETURN_THREAD_PSP;
1782#endif
1783#endif // STK_CORTEX_M_MANAGE_LR
1784
1785#if defined(STK_CORTEX_M_TRUSTZONE) && (__CORTEX_M >= 33U)
1786 // Initialize PSPLIM registers to 0 (no limit enforced until the application
1787 // sets them explicitly via CMSE APIs). They will be saved/restored per-task
1788 // by the PendSV handler from this point forward.
1789 task_frame->tz.PSPLIM = 0U;
1790 task_frame->tz.PSPLIM_NS = 0U;
1791#endif
1792
1793 return true;
1794}
1795
1796// ---------------------------------------------------------------------------
1797// ARMv8-M TrustZone Non-Secure callable (NSC) gateway veneers (Change ④)
1798//
1799// These functions are placed in the NSC-attributed linker section. The
1800// compiler inserts an SG (Secure Gateway) instruction and a BXNS return so
1801// that Non-Secure code can call into Secure-world kernel services through a
1802// hardened, auditable entry point.
1803//
1804// Calling convention: the Non-Secure caller passes arguments in r0-r3 per
1805// AAPCS. CMSE clears all other registers on entry to prevent information
1806// leakage from the Secure side.
1807//
1808// To use: declare the matching cmse_nonsecure_call function pointer in the
1809// Non-Secure image (see ITaskNonSecure_t in stk_arch_arm-cortex-m.h) and
1810// place these symbols in a region mapped as NSC in the SAU/IDAU configuration.
1811//
1812// Only compiled when STK_CORTEX_M_TRUSTZONE is defined on an ARMv8-M
1813// Mainline target (Cortex-M33 or later).
1814// ---------------------------------------------------------------------------
1815#if defined(STK_CORTEX_M_TRUSTZONE) && (__CORTEX_M >= 33U)
1816
1822__stk_nsc_entry void NSC_SwitchToNext()
1823{
1824 // Non-Secure Handler mode passes MSP_NS as the caller SP, which the
1825 // scheduler cannot use to locate a task context. Guard and return safely.
1826 if (HW_IsHandlerMode())
1827 return;
1828
1829 GetContext().m_handler->OnTaskSwitch(HW_GetCallerSP());
1830}
1831
1838__stk_nsc_entry void NSC_Sleep(Timeout ticks)
1839{
1840 if (HW_IsHandlerMode())
1841 return;
1842
1843 GetContext().m_handler->OnTaskSleep(HW_GetCallerSP(), ticks);
1844}
1845
1858__stk_nsc_entry IWaitObject *NSC_Wait(ISyncObject *sync_obj, IMutex *mutex, Timeout timeout)
1859{
1860 // Handler-mode guard: Non-Secure ISRs pass MSP_NS which cannot locate a task context.
1861 if (HW_IsHandlerMode())
1862 return nullptr;
1863
1864 // Validate that the pointers supplied by the Non-Secure caller actually
1865 // refer to Non-Secure accessible memory. If either check fails the call
1866 // is treated as an invalid request and we return nullptr immediately.
1867 if (sync_obj != nullptr &&
1868 cmse_check_pointed_object(sync_obj, CMSE_NONSECURE) == nullptr)
1869 {
1870 return nullptr;
1871 }
1872 if (mutex != nullptr &&
1873 cmse_check_pointed_object(mutex, CMSE_NONSECURE) == nullptr)
1874 {
1875 return nullptr;
1876 }
1877
1878 return GetContext().m_handler->OnTaskWait(HW_GetCallerSP(), sync_obj, mutex, timeout);
1879}
1880
1889__stk_nsc_entry TId NSC_GetTid()
1890{
1891 if (HW_IsHandlerMode())
1892 return TID_ISR;
1893
1894 return GetContext().m_handler->OnGetTid(HW_GetCallerSP());
1895}
1896
1904__stk_nsc_entry int32_t NSC_GetTickResolution()
1905{
1906 return static_cast<int32_t>(GetContext().m_tick_resolution);
1907}
1908
1909#endif // STK_CORTEX_M_TRUSTZONE && __CORTEX_M >= 33U
1910// ---------------------------------------------------------------------------
1911
1912void Context::OnStop()
1913{
1914#if STK_SEGGER_SYSVIEW
1915 SEGGER_SYSVIEW_Stop();
1916#endif
1917
1918 // stop SysTick timer
1919 HW_SysTickStop();
1920
1921 // clear pending PendSV exception
1922 SCB->ICSR = SCB_ICSR_PENDSVCLR_Msk;
1923
1924 m_started = false;
1925 m_exiting = true;
1926
1927 // make sure all assignments are set and executed
1928 __DSB();
1929 __ISB();
1930}
1931
1933{
1934 GetContext().OnStop();
1935
1936 // load context of the Exit trap
1937 HW_DisableInterrupts();
1938 OnTaskStart();
1939}
1940
1942{
1943 return GetContext().m_tick_resolution;
1944}
1945
1947{
1948 GetContext().m_handler->OnTaskSwitch(HW_GetCallerSP());
1949}
1950
1952{
1953 GetContext().m_handler->OnTaskSleep(HW_GetCallerSP(), ticks);
1954}
1955
1957{
1958 return GetContext().m_handler->OnTaskWait(HW_GetCallerSP(), sync_obj, mutex, timeout);
1959}
1960
1962{
1963 return GetContext().m_handler->OnGetTid(HW_GetCallerSP());
1964}
1965
1967{
1968 if ((GetContext().m_overrider == nullptr) || !GetContext().m_overrider->OnHardFault())
1969 {
1971 }
1972}
1973
1974void PlatformArmCortexM::SetEventOverrider(IEventOverrider *overrider)
1975{
1976 STK_ASSERT(!GetContext().m_started);
1977 GetContext().m_overrider = overrider;
1978}
1979
1981{
1982 return HW_GetCallerSP();
1983}
1984
1986{
1987 return GetContext().m_service;
1988}
1989
1991{
1992 // if we are in Handler or Privileged Thread Mode, we can skip the SVC and take the fast path
1993 if (HW_IsHandlerMode() || HW_IsPrivilegedThreadMode())
1994 GetContext().EnterCriticalSection();
1995 else
1996 GetContext().UnprivEnterCriticalSection();
1997}
1998
2000{
2001 // if we are in Handler or Privileged Thread Mode, we can skip the SVC and take the fast path
2002 if (HW_IsHandlerMode() || HW_IsPrivilegedThreadMode())
2003 GetContext().ExitCriticalSection();
2004 else
2005 GetContext().UnprivExitCriticalSection();
2006}
2007
2009{
2010 HW_SpinLockLock(m_lock);
2011}
2012
2014{
2015 HW_SpinLockUnlock(m_lock);
2016}
2017
2019{
2020 return HW_SpinLockTryLock(m_lock);
2021}
2022
2024{
2025 return HW_IsHandlerMode();
2026}
2027
2029{
2030 return HiResClockImpl::GetInstance()->GetCycles();
2031}
2032
2034{
2035 Cycles freq = HiResClockImpl::GetInstance()->GetFrequency();
2036 STK_ASSERT(freq != 0);
2037 return freq;
2038}
2039
2040#endif // _STK_ARCH_ARM_CORTEX_M
volatile uint32_t SystemCoreClock
System clock frequency in Hz.
Contains common inventory for platform implementation.
#define GetContext()
Get platform's context.
Top-level STK include. Provides the Kernel class template and all built-in task-switching strategies.
Hardware Abstraction Layer (HAL) declarations for the stk::hw namespace.
void STK_PANIC_HANDLER_DEFAULT(stk::EKernelPanicId id)
Default panic handler: disable interrupts, record the id, and spin in a tight loop — a defined,...
Definition stktest.cpp:55
#define STK_KERNEL_PANIC(id)
Called when the kernel detects an unrecoverable internal fault.
Definition stk_arch.h:63
#define __stk_attr_used
Marks a symbol as used, preventing the linker from discarding it even if no references are visible (d...
Definition stk_defs.h:172
#define __stk_forceinline
Forces compiler to always inline the decorated function, regardless of optimisation level.
Definition stk_defs.h:104
#define STK_TICKLESS_IDLE
Enables tickless (dynamic-tick) low-power operation during idle periods.
Definition stk_defs.h:36
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:330
#define __stk_attr_noinline
Prevents compiler from inlining the decorated function (function prefix).
Definition stk_defs.h:185
#define STK_CRITICAL_SECTION_NESTINGS_MAX
Maximum allowable recursion depth for critical section entry (default: 16).
Definition stk_defs.h:404
#define STK_STATIC_ASSERT_DESC_N(NAME, X, DESC)
Compile-time assertion with a user-defined name suffix and a custom error description.
Definition stk_defs.h:342
#define STK_ARCH_CPU_COUNT
Number of physical CPU cores available to the scheduler (default: 1).
Definition stk_defs.h:414
#define __stk_attr_naked
Suppresses compiler-generated function prologue and epilogue (function prefix).
Definition stk_defs.h:133
#define STK_STACK_MEMORY_FILLER
Sentinel value written to the entire stack region at initialization (stack watermark pattern).
Definition stk_defs.h:377
#define __stk_attr_noreturn
Declares that function never returns to its caller (function prefix).
Definition stk_defs.h:146
#define __stk_relax_cpu
Emits a CPU pipeline-relaxation hint for use inside hot busy-wait (spin) loops (in-code statement).
Definition stktest.h:33
Namespace of STK package.
uintptr_t Word
Native processor word type.
Definition stk_common.h:112
const TId TID_ISR
Reserved task/thread id representing an ISR context.
Definition stk_common.h:123
static __stk_forceinline Cycles ConvertTimeUsToClockCycles(Cycles clock_freq, Ticks time_us)
Convert time (microseconds) to core clock cycles.
Ticks GetTicks()
Get number of ticks elapsed since kernel start.
Definition stk_helper.h:248
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:133
EStackType
Stack type.
Definition stk_common.h:70
@ STACK_SLEEP_TRAP
Stack of the Sleep trap.
Definition stk_common.h:72
@ STACK_USER_TASK
Stack of the user task.
Definition stk_common.h:71
@ STACK_EXIT_TRAP
Stack of the Exit trap.
Definition stk_common.h:73
uint64_t Cycles
Cycles value.
Definition stk_common.h:155
Word TId
Definition stk_common.h:117
@ ACCESS_PRIVILEGED
Privileged access mode (access to hardware is fully unrestricted).
Definition stk_common.h:33
EKernelPanicId
Identifies the source of a kernel panic.
Definition stk_common.h:52
@ KERNEL_PANIC_UNKNOWN_SVC
Unknown service command received by SVC handler.
Definition stk_common.h:60
@ KERNEL_PANIC_HRT_HARD_FAULT
Kernel running in KERNEL_HRT mode reported deadline failure of the task.
Definition stk_common.h:57
@ KERNEL_PANIC_NONE
Panic is absent (no fault).
Definition stk_common.h:53
@ KERNEL_PANIC_SPINLOCK_DEADLOCK
Spin-lock timeout expired: lock owner never released.
Definition stk_common.h:54
__stk_forceinline T * WordToPtr(Word value) noexcept
Cast a CPU register-width integer back to a pointer.
Definition stk_arch.h:111
__stk_forceinline Word PtrToWord(T *ptr) noexcept
Cast a pointer to a CPU register-width integer.
Definition stk_arch.h:94
bool IsInsideISR()
Check whether the CPU is currently executing inside a hardware interrupt service routine (ISR).
Definition stktest.cpp:103
uint32_t GetTickResolution() const
Get resolution of the system tick timer in microseconds. Resolution means a number of microseconds be...
void ProcessTick()
Process one tick.
void Initialize(IEventHandler *event_handler, IKernelService *service, uint32_t resolution_us, Stack *exit_trap)
Initialize scheduler's context.
TId GetTid() const
Get thread Id.
void Sleep(Timeout ticks)
Put calling process into a sleep state.
Word GetCallerSP() const
Get caller's Stack Pointer (SP).
void Start()
Start scheduling.
void SwitchToNext()
Switch to a next task.
void Stop()
Stop scheduling.
bool InitStack(EStackType stack_type, Stack *stack, IStackMemory *stack_memory, ITask *user_task)
Initialize stack memory of the user task.
void SetEventOverrider(IEventOverrider *overrider)
Set platform event overrider.
IWaitObject * Wait(ISyncObject *sync_obj, IMutex *mutex, Timeout timeout)
void ProcessHardFault()
Cause a hard fault of the system.
Base platform context for all platform implementations.
static void Exit()
Exit a critical section.
Definition stktest.cpp:78
static void Enter()
Enter a critical section.
Definition stktest.cpp:74
bool TryLock()
Attempt to acquire SpinLock in a single non-blocking attempt.
void Lock()
Acquire SpinLock, blocking until it is available.
Definition stktest.cpp:85
void Unlock()
Release SpinLock, allowing another thread or core to acquire it.
Definition stktest.cpp:89
static uint32_t GetFrequency()
Get clock frequency.
static Cycles GetCycles()
Get number of clock cycles elapsed.
Stack descriptor.
Definition stk_common.h:181
Word SP
Stack Pointer (SP) register (note: must be the first entry in this struct).
Definition stk_common.h:182
EAccessMode mode
access mode
Definition stk_common.h:183
Interface for a stack memory region.
Definition stk_common.h:193
virtual size_t GetStackSize() const =0
Get number of elements of the stack memory array.
Wait object.
Definition stk_common.h:212
Synchronization object.
Definition stk_common.h:297
Interface for mutex synchronization primitive.
Definition stk_common.h:381
Interface for a user task.
Definition stk_common.h:433
virtual void Run()=0
Entry point of the user task.
Interface for the kernel services exposed to the user processes during run-time when Kernel started s...
Definition stk_common.h:929
static IKernelService * GetInstance()
Get CPU-local instance of the kernel service.
Definition stktest.cpp:69
RISC-V specific event handler.