diff --git a/README.md b/README.md index d8bc1d4..670cbd6 100644 --- a/README.md +++ b/README.md @@ -21,23 +21,24 @@ STK combines the control and transparency of bare-metal development with the structure and maintainability of modern, type-safe C++. -### For Embedded Developers - -- **Deterministic execution** — no dynamic memory allocation (`malloc/free`) and no heap fragmentation. Memory usage is fully predictable at compile time. -- **Lightweight C++ architecture** — clean object-oriented design without STL dependencies, exceptions, RTTI, or heavy runtime abstractions. -- **Native C interoperability** — fully featured C API available for pure C projects and mixed C/C++ codebases. -- **Transparent implementation** — minimal wrapper macros and readable scheduler internals simplify debugging and tracing. -- **Portable design** — minimal BSP surface and straightforward architecture porting. - -### For Technical Leads and Product Teams - -- **Reduced hardware requirements** — the compact kernel footprint can enable the use of lower-RAM or lower-cost MCU variants. -- **Higher CPU availability for applications** — benchmarks show up to **~12% more application CPU time** compared to FreeRTOS under comparable workloads (see [Benchmark](#benchmark)). -- **Lower power potential** — reduced scheduling overhead can help meet timing requirements at lower MCU clock frequencies. -- **Simplified migration** — compatibility layers for FreeRTOS and CMSIS-RTOS2 allow existing projects to migrate with minimal application changes. -- **Predictable system behavior** — static allocation and deterministic scheduling simplify validation, debugging, and long-term maintenance. - -> STK does not attempt to abstract or manage MCU peripherals. Its purpose is to provide a fast, predictable, and memory-efficient scheduling core for embedded applications. +You get: + +- **C++ native RTOS** — Built so the C++ compiler can efficiently optimize your STK-based application for maximum speed and ultra-low overhead. +- **Safe code from day one** — Thoughtful OOP design enforces strict encapsulation and type safety to deliver secure, robust, and high-performance firmware. +- **Full-featured RTOS** — A comprehensive suite of thread synchronization, memory, and time management primitives. You only need to bring your own HAL. +- **Safety-critical ready** — Built for strict compliance with MISRA standards. Looking for a safe C++ RTOS for your certified device? Explore our [Services](#services). +- **Deterministic execution** — Zero dynamic memory allocation (`malloc`/`free`) and zero heap fragmentation. Memory usage is fully predictable at compile time. +- **Clean C++ design** — No STL dependencies, exceptions, RTTI, or heavy runtime abstractions. Readable internals simplify debugging and tracing. +- **Verbose-free code** — A clean C++ API makes your implementation highly concise, making it significantly easier to maintain, refactor, and debug than standard C-only APIs. +- **Portable design** — Minimal BSP (Board Support Package) footprint with complete independence from specific board and MCU peripherals. +- **Reduced hardware requirements** — Compact kernel footprint that allows you to deploy on lower-RAM, lower-cost MCU variants. +- **Higher CPU availability** — More time for your application logic. Benchmarks show up to **~12% more application CPU time** compared to FreeRTOS under comparable workloads (see [Benchmark](#benchmark)). +- **Lower power consumption** — Features ultra-low power, tickless scheduling paired with reduced overhead, enabling the use of lower-frequency MCUs to save battery of your portable design. +- **Native C support** — Includes a fully featured C API wrapper, allowing you to seamlessly use STK in pure C projects. +- **Simplified migration** — Drop-in compatibility layers for FreeRTOS and CMSIS-RTOS2 to help you migrate legacy codebases with minimal application changes. +- **B2B professional support** — Engineered for seamless integration into commercial projects. Explore our [Services](#services) for enterprise-grade support and custom engineering. + +> STK does not attempt to abstract or manage MCU peripherals, similarly to FreeRTOS or CMSIS-RTOS2. STK is an open-source project developed at https://github.com/SuperTinyKernel-RTOS. @@ -326,6 +327,13 @@ A complete ultra-low power demo targeting the [STM32F407G-DISC1](https://www.st. --- +## Language + +* Minimal: C++11 +* Recommended: C++17 and higher + +--- + ## Dedicated C interface For seamless integration with C projects, STK provides a dedicated, fully-featured C API. See [interop/c](https://github.com/SuperTinyKernel-RTOS/stk/tree/main/interop/c) for the full reference and examples. @@ -963,7 +971,7 @@ You may freely use it in projects of any type: --- -## 🔒 Professional Services & Commercial Licensing +## Services While **SuperTinyKernel™ RTOS** is provided under the permissive MIT license, we offer dedicated professional services for organizations integrating STK into production-grade, mission-critical, or regulated environments. diff --git a/build/benchmark/eclipse/perf-stm32f407g-disc1-stk/src/main.cpp b/build/benchmark/eclipse/perf-stm32f407g-disc1-stk/src/main.cpp index f7f0d75..80cf205 100644 --- a/build/benchmark/eclipse/perf-stm32f407g-disc1-stk/src/main.cpp +++ b/build/benchmark/eclipse/perf-stm32f407g-disc1-stk/src/main.cpp @@ -32,7 +32,7 @@ extern "C" void SysTick_Handler() if (g_Enable) ++g_Ticks; - if (g_Kernel.GetState() == stk::IKernel::STATE_RUNNING) + if (g_Kernel.GetState() == stk::IKernel::KSTATE_RUNNING) g_Kernel.GetPlatform()->ProcessTick(); } diff --git a/build/example/driver/led.h b/build/example/driver/led.h index 25ccf53..af8bff3 100644 --- a/build/example/driver/led.h +++ b/build/example/driver/led.h @@ -39,14 +39,18 @@ struct Led static void Init(Id led, bool init_state); static inline void InitAll(bool init_state) { - for (uint8_t led = 0; led < LED_MAX; ++led) + for (uint8_t led = 0U; led < LED_MAX; ++led) + { Led::Init(static_cast(led), init_state); + } } static void Set(Id led, bool state); static void SwitchOnExclusive(Id led) { - for (uint8_t i = 0; i < LED_MAX; ++i) + for (uint8_t i = 0U; i < LED_MAX; ++i) + { Led::Set(static_cast(i), (i == led)); + } } }; diff --git a/interop/c/include/stk_c.h b/interop/c/include/stk_c.h index 9532239..3a16d0b 100644 --- a/interop/c/include/stk_c.h +++ b/interop/c/include/stk_c.h @@ -321,18 +321,18 @@ void stk_kernel_start(stk_kernel_t *k); \note It is a direct match for IKernel::EState enum. \see stk_kernel_get_state() */ -typedef enum _EKernelState { +typedef enum stk_kernel_state_t { STK_KERNEL_STATE_INACTIVE = 0, //!< not ready, stk_kernel_init() must be called STK_KERNEL_STATE_READY = 1, //!< ready to start, stk_kernel_start() must be called STK_KERNEL_STATE_RUNNING = 2, //!< initialized and running, stk_kernel_start() was called successfully STK_KERNEL_STATE_SUSPENDED = 3 //!< scheduling suspended via stk_kernel_service_suspend() (tickless idle) -} EKernelState; +} stk_kernel_state_t; /*! \brief Get state of the scheduler. \param[in] k: Kernel handle. - \return State value, see \a EKernelState. + \return State value, see \a stk_kernel_state_t. */ -EKernelState stk_kernel_get_state(const stk_kernel_t *k); +stk_kernel_state_t stk_kernel_get_state(const stk_kernel_t *k); /*! \brief Test whether currently configured task set is schedulable. \param[in] k: Kernel handle. @@ -567,7 +567,7 @@ stk_tick_t stk_ticks(void); /*! \brief Returns how many microseconds correspond to one kernel tick. \return Tick resolution in microseconds. */ -int32_t stk_tick_resolution(void); +uint32_t stk_tick_resolution(void); /*! \brief Get ticks from milliseconds using current kernel tick resolution. \param[in] msec: Milliseconds to convert. @@ -763,12 +763,12 @@ void stk_tls_set(void *ptr); /*! \brief Typed helper for getting TLS value. \note Expands to ((type *)stk_tls_get()) */ -#define STK_TLS_GET(type) ((type *)stk_tls_get()) +#define STK_TLS_GET_T(type) ((type *)stk_tls_get()) /*! \brief Typed helper for setting TLS value. \note Expands to stk_tls_set((void *)(ptr)) */ -#define STK_TLS_SET(ptr) stk_tls_set((void *)(ptr)) +#define STK_TLS_SET_T(ptr) stk_tls_set((void *)(ptr)) // ============================================================================= // Synchronization Primitives diff --git a/interop/c/include/stk_c_memory.h b/interop/c/include/stk_c_memory.h index a05e0e1..151e4cd 100644 --- a/interop/c/include/stk_c_memory.h +++ b/interop/c/include/stk_c_memory.h @@ -71,7 +71,7 @@ extern "C" { \note Increase if your application needs more simultaneous pools. */ #ifndef STK_C_BLOCKPOOL_MAX - #define STK_C_BLOCKPOOL_MAX 8 + #define STK_C_BLOCKPOOL_MAX 8U #endif // ============================================================================= diff --git a/interop/c/src/stk_c.cpp b/interop/c/src/stk_c.cpp index 915b734..a9fe6b0 100644 --- a/interop/c/src/stk_c.cpp +++ b/interop/c/src/stk_c.cpp @@ -139,17 +139,25 @@ class EventOverrider final : public IPlatform::IEventOverrider // IPlatform::IEventOverrider bool OnSleep(Timeout sleep_ticks) override { + bool is_handled = false; + if ((m_cb != nullptr) && (m_cb->on_sleep != nullptr)) - return m_cb->on_sleep(static_cast(sleep_ticks), m_cb->user_data); + { + is_handled = m_cb->on_sleep(static_cast(sleep_ticks), m_cb->user_data); + } - return false; + return is_handled; } bool OnHardFault() override { + bool is_handled = false; + if ((m_cb != nullptr) && (m_cb->on_hard_fault != nullptr)) - return m_cb->on_hard_fault(m_cb->user_data); + { + is_handled = m_cb->on_hard_fault(m_cb->user_data); + } - return false; + return is_handled; } private: @@ -320,7 +328,6 @@ void stk_kernel_destroy(stk_kernel_t *k) // GetPlatform() paths, and we must not leave a dangling overrider pointer // registered at that point. UnregisterKernel(reinterpret_cast(k)); - reinterpret_cast(k)->~IKernel(); } // ----------------------------------------------------------------------------- @@ -340,11 +347,11 @@ void stk_kernel_start(stk_kernel_t *k) reinterpret_cast(k)->Start(); } -EKernelState stk_kernel_get_state(const stk_kernel_t *k) +stk_kernel_state_t stk_kernel_get_state(const stk_kernel_t *k) { STK_ASSERT(k != nullptr); - return static_cast(reinterpret_cast(k)->GetState()); + return static_cast(reinterpret_cast(k)->GetState()); } bool stk_kernel_is_schedulable(const stk_kernel_t *k) @@ -375,8 +382,8 @@ bool stk_kernel_is_started(const stk_kernel_t *k) { STK_ASSERT(k != nullptr); - const stk::IKernel::EState st = reinterpret_cast(k)->GetState(); - return ((st == stk::IKernel::STATE_RUNNING) || (st == stk::IKernel::STATE_SUSPENDED)); + const stk::IKernel::EKernelState st = reinterpret_cast(k)->GetState(); + return ((st == stk::IKernel::KSTATE_RUNNING) || (st == stk::IKernel::KSTATE_SUSPENDED)); } void stk_kernel_schedule_task_removal(stk_kernel_t *k, stk_task_t *task) @@ -409,16 +416,23 @@ size_t stk_kernel_enumerate_tasks(stk_kernel_t *k, stk_task_t **tasks, size_t ma STK_ASSERT(k != nullptr); STK_ASSERT(tasks != nullptr); - // Collect ITask* pointers from the kernel, then map each back to stk_task_t*. - // TaskWrapper is the first member of stk_task_t, so ITask* == stk_task_t*. stk::ITask *itasks[STK_C_TASKS_MAX] = {}; - size_t n = reinterpret_cast(k)->EnumerateTasks( - itasks, (max_count < STK_C_TASKS_MAX ? max_count : STK_C_TASKS_MAX)); + + // Determine the safe upper bound for the temporary buffer + const size_t requested_size = (max_count < static_cast(STK_C_TASKS_MAX)) ? max_count : + static_cast(STK_C_TASKS_MAX); + + // Pass via ArrayView temporary object + const size_t ret_count = reinterpret_cast(k)->EnumerateTasks( + ArrayView(itasks, requested_size)); - for (size_t i = 0; i < n; ++i) - tasks[i] = reinterpret_cast(itasks[i]); + ArrayView output_view(tasks, max_count); + for (size_t i = 0U; i < ret_count; ++i) + { + output_view[i] = reinterpret_cast(itasks[i]); + } - return n; + return ret_count; } stk_timeout_t stk_kernel_suspend(stk_kernel_t *k) @@ -549,7 +563,7 @@ void stk_task_destroy(stk_task_t *task) // ----------------------------------------------------------------------------- stk_tid_t stk_tid(void) { return stk::GetTid(); } stk_tick_t stk_ticks(void) { return stk::GetTicks(); } -int32_t stk_tick_resolution(void) { return stk::GetTickResolution(); } +uint32_t stk_tick_resolution(void) { return stk::GetTickResolution(); } stk_time_t stk_time_now_ms(void) { return stk::GetTimeNowMs(); } stk_tick_t stk_ticks_from_ms(stk_time_t msec) { return stk_ticks_from_ms_r(msec, stk::GetTickResolution()); } stk_cycle_t stk_sys_timer_count(void) { return stk::GetSysTimerCount(); } diff --git a/interop/c/src/stk_c_memory.cpp b/interop/c/src/stk_c_memory.cpp index 7cabd9f..95a9693 100644 --- a/interop/c/src/stk_c_memory.cpp +++ b/interop/c/src/stk_c_memory.cpp @@ -34,7 +34,7 @@ static_assert( // Returns a size of memory in stk::Word elements required for object allocation. template static constexpr size_t StkGetWordCountForType() { - return ((sizeof(T) + sizeof(stk::Word) - 1) / sizeof(stk::Word)); + return ((sizeof(T) + sizeof(stk::Word) - 1U) / sizeof(stk::Word)); } // Private memory allocators (we define malloc, free here to overcome absence of declaration in @@ -92,18 +92,22 @@ s_BlockPools[STK_C_BLOCKPOOL_MAX]; // Find the slot that owns 'pool'; returns nullptr if not found. static BlockPoolSlot *FindSlot(const stk_blockpool_t *pool) { - for (uint32_t i = 0; i < STK_C_BLOCKPOOL_MAX; ++i) + for (size_t i = 0U; i < STK_C_BLOCKPOOL_MAX; ++i) { - if (s_BlockPools[i].busy && (s_BlockPools[i].pool() == pool)) - return &s_BlockPools[i]; + if (s_BlockPools[i].busy) + { + if (s_BlockPools[i].pool() == pool) + return &s_BlockPools[i]; + } } + return nullptr; } // Acquire a free slot; returns nullptr when the pool is exhausted. static BlockPoolSlot *AcquireSlot() { - for (uint32_t i = 0; i < STK_C_BLOCKPOOL_MAX; ++i) + for (size_t i = 0U; i < STK_C_BLOCKPOOL_MAX; ++i) { if (!s_BlockPools[i].busy) { @@ -111,6 +115,7 @@ static BlockPoolSlot *AcquireSlot() return &s_BlockPools[i]; } } + return nullptr; } @@ -128,7 +133,7 @@ stk_blockpool_t *stk_blockpool_create(size_t capacity, size_t raw_block_size, co STK_ASSERT(capacity > 0U); STK_ASSERT(raw_block_size > 0U); - sync::ScopedCriticalSection __cs; + const sync::ScopedCriticalSection cs_; BlockPoolSlot *slot = AcquireSlot(); if (slot == nullptr) @@ -152,7 +157,7 @@ stk_blockpool_t *stk_blockpool_create_static(size_t capacity, STK_ASSERT(storage != nullptr); STK_ASSERT(storage_size >= (capacity * BlockMemoryPool::AlignBlockSize(raw_block_size))); - sync::ScopedCriticalSection __cs; + sync::ScopedCriticalSection cs_; BlockPoolSlot *slot = AcquireSlot(); if (slot == nullptr) @@ -170,7 +175,7 @@ void stk_blockpool_destroy(stk_blockpool_t *pool) { STK_ASSERT(pool != nullptr); - sync::ScopedCriticalSection __cs; + sync::ScopedCriticalSection cs_; BlockPoolSlot *slot = FindSlot(pool); diff --git a/interop/cmsis/rtos2/src/cmsis_os2_stk.cpp b/interop/cmsis/rtos2/src/cmsis_os2_stk.cpp index bf4c968..cf09115 100644 --- a/interop/cmsis/rtos2/src/cmsis_os2_stk.cpp +++ b/interop/cmsis/rtos2/src/cmsis_os2_stk.cpp @@ -179,7 +179,7 @@ struct StkThread final : public stk::ITask } // ---- IStackMemory ---- - stk::Word *GetStack() const override { return m_stack; } + const stk::Word *GetStack() const override { return m_stack; } size_t GetStackSize() const override { return m_stack_size; } size_t GetStackSizeBytes() const override { return m_stack_size * sizeof(stk::Word); } @@ -510,11 +510,11 @@ osKernelState_t osKernelGetState(void) switch (g_StkKernel.GetState()) { - case stk::IKernel::STATE_INACTIVE: return osKernelInactive; - case stk::IKernel::STATE_READY: return osKernelReady; - case stk::IKernel::STATE_RUNNING: return osKernelRunning; - case stk::IKernel::STATE_SUSPENDED: return osKernelSuspended; - default: return osKernelError; + case stk::IKernel::KSTATE_INACTIVE: return osKernelInactive; + case stk::IKernel::KSTATE_READY: return osKernelReady; + case stk::IKernel::KSTATE_RUNNING: return osKernelRunning; + case stk::IKernel::KSTATE_SUSPENDED: return osKernelSuspended; + default: return osKernelError; } } @@ -959,13 +959,24 @@ uint32_t osThreadGetCount(void) uint32_t osThreadEnumerate(osThreadId_t *thread_array, uint32_t array_items) { - if (osKernelGetState() == osKernelInactive) - return 0U; + uint32_t result_count = 0U; + const osKernelState_t kstate = osKernelGetState(); - // osThreadId_t maps directly to stk::ITask (see StkThread) - return g_StkKernel.EnumerateTasks(reinterpret_cast(thread_array), array_items); -} + // kernel must be active and buffer must be valid + if ((kstate != osKernelInactive) && (thread_array != nullptr) && (array_items != 0U)) + { + // cast the raw pointer array to the expected ITask* destination type + stk::ITask **tasks_destination = reinterpret_cast(thread_array); + // bind raw destination buffer into a temporary ArrayView object + const size_t count = g_StkKernel.EnumerateTasks( + stk::ArrayView(tasks_destination, static_cast(array_items))); + + result_count = static_cast(count); + } + + return result_count; +} // =========================================================================== // ==== Thread Flags Functions ==== diff --git a/interop/freertos/src/freertos_stk.cpp b/interop/freertos/src/freertos_stk.cpp index c47209f..f26d029 100644 --- a/interop/freertos/src/freertos_stk.cpp +++ b/interop/freertos/src/freertos_stk.cpp @@ -417,7 +417,7 @@ struct FrtosTask : public stk::ITask m_state = State::Deleted; } - stk::Word *GetStack() const override { return m_stack; } + const stk::Word *GetStack() const override { return m_stack; } size_t GetStackSize() const override { return m_stack_size; } size_t GetStackSizeBytes() const override { return m_stack_size * sizeof(stk::Word); } stk::EAccessMode GetAccessMode() const override { return stk::ACCESS_PRIVILEGED; } @@ -432,16 +432,16 @@ struct FrtosTask : public stk::ITask size_t GetStackHighWaterMark() const { return GetStackSpace(); } // ---- Members ---- - TaskFunction_t m_func; - void *m_argument; - const char *m_name; - volatile int32_t m_weight; // STK SWRR weight (priority+1) - stk::Word *m_stack; - size_t m_stack_size; // Words - bool m_stack_owned; - bool m_cb_owned; // true -> heap-alloc, delete on removal - volatile State m_state; - uint32_t m_task_number; // monotonic serial, assigned at construction + TaskFunction_t m_func; + void *m_argument; + const char *m_name; + volatile int32_t m_weight; // STK SWRR weight (priority+1) + stk::Word *m_stack; + size_t m_stack_size; // Words + bool m_stack_owned; + bool m_cb_owned; // true -> heap-alloc, delete on removal + volatile State m_state; + uint32_t m_task_number; // monotonic serial, assigned at construction // Monotonic counter incremented once per FrtosTask construction. // Stored as a file-scope static so all tasks share a single sequence. @@ -1030,8 +1030,10 @@ struct FrtosMessageBuffer // Ensure kernel is initialized. static void EnsureKernelInitialized() { - if (g_StkKernel.GetState() == stk::IKernel::STATE_INACTIVE) + if (g_StkKernel.GetState() == stk::IKernel::KSTATE_INACTIVE) + { g_StkKernel.Initialize(); // default 1 ms tick resolution + } } // =========================================================================== @@ -1091,9 +1093,9 @@ BaseType_t xTaskGetSchedulerState(void) // STATE_SUSPENDED -> SUSPENDED switch (g_StkKernel.GetState()) { - case stk::IKernel::STATE_RUNNING: return taskSCHEDULER_RUNNING; - case stk::IKernel::STATE_SUSPENDED: return taskSCHEDULER_SUSPENDED; - default: return taskSCHEDULER_NOT_STARTED; + case stk::IKernel::KSTATE_RUNNING: return taskSCHEDULER_RUNNING; + case stk::IKernel::KSTATE_SUSPENDED: return taskSCHEDULER_SUSPENDED; + default: return taskSCHEDULER_NOT_STARTED; } } diff --git a/stk/include/arch/arm/cortex-m/stk_arch_arm-cortex-m.h b/stk/include/arch/arm/cortex-m/stk_arch_arm-cortex-m.h index 17480af..90a58a4 100644 --- a/stk/include/arch/arm/cortex-m/stk_arch_arm-cortex-m.h +++ b/stk/include/arch/arm/cortex-m/stk_arch_arm-cortex-m.h @@ -27,7 +27,7 @@ class PlatformArmCortexM final : public IPlatform /*! \brief Destructor. \note MISRA deviation: [STK-DEV-005] Rule 10-3-2. */ - ~PlatformArmCortexM() = default; + STK_VIRT_DTOR ~PlatformArmCortexM() = default; void Initialize(IEventHandler *event_handler, IKernelService *service, uint32_t resolution_us, Stack *exit_trap) override; void Start() override; @@ -104,7 +104,7 @@ __stk_forceinline void SetTls(Word tp) /*! \def __stk_dmb \brief Hardware memory barrier: ensures visibility across cores and bus masters. */ -#define __stk_dmb() __asm volatile("dmb sy" ::: "memory") +static __stk_forceinline void __stk_dmb() { __asm volatile("dmb sy" ::: "memory"); } /*! \def __stk_tz_nsc_entry \brief TrustZone: attribute for Non-Secure callable gateway functions. diff --git a/stk/include/arch/risc-v/stk_arch_risc-v.h b/stk/include/arch/risc-v/stk_arch_risc-v.h index 24642f6..3662f99 100644 --- a/stk/include/arch/risc-v/stk_arch_risc-v.h +++ b/stk/include/arch/risc-v/stk_arch_risc-v.h @@ -101,7 +101,7 @@ __stk_forceinline void SetTls(Word tp) /*! \def __stk_dmb \brief Data memory barrier. */ -#define __stk_dmb() __asm volatile("fence rw, rw" ::: "memory") +static __stk_forceinline void __stk_dmb() { __asm volatile("fence rw, rw" ::: "memory"); } /*! \def STK_SUBMICORSECOND_PRECISION_TIMER \brief Enables sub-microsecond precision timer, see \a hw::HiResClock. diff --git a/stk/include/arch/stk_arch_common.h b/stk/include/arch/stk_arch_common.h index 516f387..4b30ee9 100644 --- a/stk/include/arch/stk_arch_common.h +++ b/stk/include/arch/stk_arch_common.h @@ -31,7 +31,7 @@ class PlatformContext /*! \brief Destructor. \note MISRA deviation: [STK-DEV-005] Rule 10-3-2. */ - ~PlatformContext() = default; + STK_VIRT_DTOR ~PlatformContext() = default; /*! \brief Initialize context. \param[in] handler: Event handler. @@ -51,24 +51,26 @@ class PlatformContext /*! \brief Initialize stack memory by filling it with STK_STACK_MEMORY_FILLER. \note Returned pointer is for a stack growing from top to down. \param[in] memory: Stack memory to initialize. - \return Pointer to initialized stack memory. + \return Top of the stack memory (valid memory region is (stack_top - sizeof(Word))). */ - static inline Word *InitStackMemory(IStackMemory *memory) + static inline Word InitStackMemory(IStackMemory *const memory) { - const size_t stack_size = memory->GetStackSize(); - Word *itr = const_cast(memory->GetStack()); - Word *const stack_top = itr + stack_size; - - STK_ASSERT(stack_size >= STACK_SIZE_MIN); - - // initialization of the stack memory satisfies stack integrity check in Kernel::StateSwitch - while (itr < stack_top) + STK_ASSERT(memory != nullptr); + + ArrayView stack(const_cast(memory->GetStack()), memory->GetStackSize()); + STK_ASSERT(stack.GetSize() >= STACK_SIZE_MIN); + + // initialize stack memory to satisfy stack integrity check in Kernel::StateSwitch + for (size_t i = 0U; i < stack.GetSize(); ++i) { - *itr++ = STK_STACK_MEMORY_FILLER; + stack[i] = STK_STACK_MEMORY_FILLER; } + + // get address of the last valid item and step forward by 1 Word size + const Word stack_top = hw::PtrToWord(memory->GetStack()) + (stack.GetSize() * sizeof(Word)); // expecting STK_STACK_MEMORY_ALIGN-byte aligned memory for a stack - STK_ASSERT((hw::PtrToWord(stack_top) & (STK_STACK_MEMORY_ALIGN - 1)) == 0U); + STK_ASSERT((stack_top & (STK_STACK_MEMORY_ALIGN - 1U)) == 0U); return stack_top; } @@ -102,9 +104,9 @@ class PlatformContext \param[in] time_us: Time (microseconds). \return Clock cycles. */ -static __stk_forceinline Cycles ConvertTimeUsToClockCycles(Cycles clock_freq, Ticks time_us) +static __stk_forceinline Cycles ConvertTimeUsToClockCycles(uint32_t clock_freq, Ticks time_us) { - return ((clock_freq * static_cast(time_us)) / 1000000ULL); + return ((static_cast(time_us) * clock_freq) / 1000000ULL); } } // namespace stk diff --git a/stk/include/arch/x86/win32/stk_arch_x86-win32.h b/stk/include/arch/x86/win32/stk_arch_x86-win32.h index 0a68f75..db637a9 100644 --- a/stk/include/arch/x86/win32/stk_arch_x86-win32.h +++ b/stk/include/arch/x86/win32/stk_arch_x86-win32.h @@ -65,13 +65,13 @@ typedef PlatformX86Win32 PlatformDefault; #include #if defined(_M_IX86) || defined(_M_X64) // x86/x64: full hardware serializing fence - #define __stk_dmb() _mm_mfence() + static __stk_forceinline void __stk_dmb() { _mm_mfence(); } #elif defined(_M_ARM) || defined(_M_ARM64) // ARM/ARM64: Data Memory Barrier (Inner Shareable) - #define __stk_dmb() __dmb(_ARM_BARRIER_ISH) + static __stk_forceinline void __stk_dmb() { __dmb(_ARM_BARRIER_ISH); } #endif #elif defined(__GNUC__) || defined(__clang__) - #define __stk_dmb() __sync_synchronize() + static __stk_forceinline void __stk_dmb() { __sync_synchronize(); } #else #error "__stk_dmb() is not implemented for this compiler." #endif diff --git a/stk/include/memory/stk_memory_allocator.h b/stk/include/memory/stk_memory_allocator.h index 8c8ebb6..47275cb 100644 --- a/stk/include/memory/stk_memory_allocator.h +++ b/stk/include/memory/stk_memory_allocator.h @@ -65,7 +65,8 @@ struct MemoryAllocator struct Stats { Stats(size_t _capacity = CAPACITY_DEFAULT) - : capacity(_capacity), allocated(0U), allocate_count(0U), free_count(0U), min_ever_free(_capacity) + : capacity(_capacity), allocated(0U), allocate_count(0U), free_count(0U), + min_ever_free(_capacity) {} const size_t capacity; //!< Total capacity of the memory pool in bytes. @@ -137,8 +138,11 @@ struct MemoryAllocator TElement *ptr = reinterpret_cast(Allocate(sizeof(TElement))); if (ptr != nullptr) { - if (!std::is_trivially_constructible()) - new (ptr) TElement(static_cast(args)...); + if __stk_constexpr_cpp17 (!std::is_trivially_constructible()) + { + auto const elm = new (ptr) TElement(static_cast(args)...); + STK_UNUSED(elm); + } } return ptr; @@ -153,18 +157,23 @@ struct MemoryAllocator template static inline TElement *AllocateArrayT(size_t count, TArgs &&...args) { - if (count == 0) - return nullptr; - - STK_ASSERT(Allocate != nullptr); - - TElement *ptr = reinterpret_cast(Allocate(count * sizeof(TElement))); - if (ptr != nullptr) + TElement *ptr = nullptr; + + if (count != 0U) { - if (!std::is_trivially_constructible()) + STK_ASSERT(Allocate != nullptr); + + ptr = reinterpret_cast(Allocate(count * sizeof(TElement))); + if (ptr != nullptr) { - for (size_t i = 0; i < count; ++i) - new (&ptr[i]) TElement(static_cast(args)...); + if __stk_constexpr_cpp17 (!std::is_trivially_constructible()) + { + for (size_t i = 0U; i < count; ++i) + { + auto const elm = new (&ptr[i]) TElement(static_cast(args)...); + STK_UNUSED(elm); + } + } } } @@ -181,8 +190,10 @@ struct MemoryAllocator if (ptr != nullptr) { - if (!std::is_trivially_destructible()) + if __stk_constexpr_cpp17 (!std::is_trivially_destructible()) + { ptr->~TElement(); + } Free(ptr); } @@ -199,11 +210,17 @@ struct MemoryAllocator if (ptr != nullptr) { - if (!std::is_trivially_destructible()) + if __stk_constexpr_cpp17 (!std::is_trivially_destructible()) { // destroy in reverse order (mirrors stack unwinding) - for (size_t i = count; i > 0; --i) + for (size_t i = count; i > 0U; --i) + { ptr[i - 1].~TElement(); + } + } + else + { + STK_UNUSED(count); } Free(ptr); diff --git a/stk/include/memory/stk_memory_blockpool.h b/stk/include/memory/stk_memory_blockpool.h index 98f6fce..751636a 100644 --- a/stk/include/memory/stk_memory_blockpool.h +++ b/stk/include/memory/stk_memory_blockpool.h @@ -143,7 +143,7 @@ class BlockMemoryPool : public ITraceable is triggered in debug builds inside the \c ConditionVariable destructor. \note MISRA deviation: [STK-DEV-005] Rule 10-3-2. */ - ~BlockMemoryPool(); + STK_VIRT_DTOR ~BlockMemoryPool(); /*! \brief Round a raw block size up to the nearest multiple of \c BLOCK_ALIGN. \details Enforces a minimum of \c BLOCK_ALIGN so the free-list link always @@ -164,24 +164,24 @@ class BlockMemoryPool : public ITraceable \details If the pool is empty the calling task is suspended via the internal \c ConditionVariable and woken by the next \c Free() call. On return the caller owns the block and must eventually return it via \c Free(). - \param[in] timeout: Maximum time to wait (ticks). + \param[in] timeout_ticks: Maximum time to wait (ticks). \c WAIT_INFINITE - block indefinitely (default). \c NO_WAIT - return \c nullptr immediately if pool is empty (identical to \c TryAlloc(); ISR-safe). \return Pointer to an uninitialized block of at least \c GetRawBlockSize() bytes, or \c nullptr if the timeout expired before a block became available. - \warning ISR-safe \b only when \a timeout = \c NO_WAIT; ISR-unsafe otherwise. + \warning ISR-safe \b only when \a timeout_ticks = \c NO_WAIT; ISR-unsafe otherwise. */ - void *TimedAlloc(Timeout timeout = WAIT_INFINITE); + void *TimedAlloc(Timeout timeout_ticks = WAIT_INFINITE); /*! \brief Allocate one typed block, blocking until one becomes available or the timeout expires. \details Thin typed wrapper around \c TimedAlloc(). Asserts that \c sizeof(T) fits within the aligned block size chosen at construction. - \param[in] timeout: Maximum time to wait (ticks). Same semantics as \c TimedAlloc(). + \param[in] timeout_ticks: Maximum time to wait (ticks). Same semantics as \c TimedAlloc(). \return Typed pointer, or \c nullptr if the timeout expired. - \warning ISR-safe \b only when \a timeout = \c NO_WAIT; ISR-unsafe otherwise. + \warning ISR-safe \b only when \a timeout_ticks = \c NO_WAIT; ISR-unsafe otherwise. */ - template T *TimedAllocT(Timeout timeout = WAIT_INFINITE); + template T *TimedAllocT(Timeout timeout_ticks = WAIT_INFINITE); /*! \brief Allocate one block, blocking indefinitely until one is available. \return Pointer to an uninitialized block. Never returns \c nullptr. @@ -316,7 +316,7 @@ class BlockMemoryPool : public ITraceable sync::ConditionVariable m_cv; //!< signalled by Free() to wake one task blocked in TimedAlloc() size_t m_block_size; //!< aligned block size in bytes (>= BLOCK_ALIGN) size_t m_capacity; //!< total number of blocks - uint16_t m_used_count; //!< number of blocks currently allocated (outstanding) + size_t m_used_count; //!< number of blocks currently allocated (outstanding) bool m_storage_owned; //!< true -> storage is heap-allocated; free in destructor }; @@ -343,7 +343,9 @@ inline BlockMemoryPool::BlockMemoryPool(size_t capacity, size_t raw_block_size, // in Release builds we ensure capacity which fits storage size, in Debug build the assertion above will be hit if ((capacity * m_block_size) > storage_size) + { m_capacity = storage_size / m_block_size; + } #if STK_SYNC_DEBUG_NAMES SetTraceName(name); @@ -374,7 +376,9 @@ inline BlockMemoryPool::BlockMemoryPool(size_t capacity, size_t raw_block_size, #endif if (m_storage != nullptr) + { BuildFreeList(); + } // else: m_free_list remains nullptr; caller must check IsStorageValid() } #endif @@ -395,33 +399,46 @@ inline BlockMemoryPool::~BlockMemoryPool() // Alloc / TimedAlloc / TryAlloc // --------------------------------------------------------------------------- -inline void *BlockMemoryPool::TimedAlloc(Timeout timeout) +inline void *BlockMemoryPool::TimedAlloc(Timeout timeout_ticks) { - if (hw::IsInsideISR() && (timeout != NO_WAIT)) + void *block = nullptr; + + if (!hw::IsInsideISR() || (timeout_ticks == NO_WAIT)) { - STK_ASSERT(false); // API contract: ISR callers must pass NO_WAIT / use TryAlloc() - return nullptr; - } + sync::ScopedCriticalSection cs_; + bool is_timeout = false; - sync::ScopedCriticalSection cs_; + while (m_free_list == nullptr) + { + // Atomically release the critical section, suspend the task, and + // re-acquire before returning - no CPU cycles wasted while waiting. + if (!m_cv.Wait(cs_, timeout_ticks)) + { + is_timeout = true; // timeout expired + break; + } + } - while (m_free_list == nullptr) + // only allocate a block if we didn't time out + if (!is_timeout) + { + block = PopFreeList(); + } + } + else { - // Atomically release the critical section, suspend the task, and - // re-acquire before returning - no CPU cycles wasted while waiting. - if (!m_cv.Wait(cs_, timeout)) - return nullptr; // timeout expired + STK_ASSERT(false); // API contract: ISR callers must pass NO_WAIT / use TryAlloc() } - return PopFreeList(); + return block; } template -inline T *BlockMemoryPool::TimedAllocT(Timeout timeout) +inline T *BlockMemoryPool::TimedAllocT(Timeout timeout_ticks) { STK_ASSERT(sizeof(T) <= m_block_size); // API contract: block size should larger or equal to object's size - return static_cast(TimedAlloc(timeout)); + return static_cast(TimedAlloc(timeout_ticks)); } inline void *BlockMemoryPool::Alloc() @@ -452,58 +469,68 @@ inline T *BlockMemoryPool::TryAllocT() inline bool BlockMemoryPool::Free(void *ptr) { - if (ptr == nullptr) - return false; + bool success = false; - // bounds check: ptr must be in range [m_storage, m_storage + capacity * block_size) - const uint8_t *p8 = static_cast(ptr); - const uint8_t *lo = m_storage; - const uint8_t *hi = m_storage + (m_capacity * m_block_size); - - if ((p8 < lo) || (p8 >= hi)) + if (ptr != nullptr) { - STK_ASSERT(false); // API contract: ptr does not belong to this pool - return false; - } + // bounds check: ptr must be in range [m_storage, m_storage + capacity * block_size) + const Word pt = hw::PtrToWord(ptr); + const Word lo = hw::PtrToWord(m_storage); + const Word hi = lo + (m_capacity * m_block_size); - // alignment check: ptr must be at the start of a block boundary - if ((static_cast(p8 - lo) % m_block_size) != 0U) - { - STK_ASSERT(false); // API contract: ptr is misaligned (not a block start) - return false; - } - - sync::ScopedCriticalSection cs_; - - if (m_used_count == 0U) - { - STK_ASSERT(false); // pool is already fully free - definite double-free - return false; - } - -#if defined(_DEBUG) || defined(DEBUG) - // O(n) double-free detection: walk the free-list and check if ptr is already on it, - // only active in debug builds - compiles away completely in release - for (const MemoryBlock *node = m_free_list; (node != nullptr); node = node->next) - { - if (node == reinterpret_cast(ptr)) + if ((pt < lo) || (pt >= hi)) { - STK_ASSERT(false); // double-free: ptr is already on the free-list - return false; + STK_ASSERT(false); // API contract: ptr does not belong to this pool + } + // alignment check: ptr must be at the start of a block boundary + else if ((static_cast(pt - lo) % m_block_size) != 0U) + { + STK_ASSERT(false); // API contract: ptr is misaligned (not a block start) + } + else + { + const sync::ScopedCriticalSection cs_; + + if (m_used_count == 0U) + { + STK_ASSERT(false); // pool is already fully free - definite double-free + } + else + { + #if defined(_DEBUG) || defined(DEBUG) + bool is_double_free = false; + + // O(n) double-free detection: walk the free-list and check if ptr is already on it, + // only active in debug builds - compiles away completely in release + for (const MemoryBlock *node = m_free_list; (node != nullptr); node = node->next) + { + if (node == reinterpret_cast(ptr)) + { + STK_ASSERT(false); // double-free: ptr is already on the free-list + is_double_free = true; + break; + } + } + + if (!is_double_free) + #endif + { + // push block onto free-list head (O(1)) + auto *const blk = reinterpret_cast(ptr); + blk->next = m_free_list; + m_free_list = blk; + m_used_count = static_cast(m_used_count - 1U); + + // wake one blocked allocator - true scheduler wait, no spin-yield + m_cv.NotifyOne_CS(); + + success = true; + } + } } } -#endif - - // push block onto free-list head (O(1)) - auto *blk = reinterpret_cast(ptr); - blk->next = m_free_list; - m_free_list = blk; - m_used_count = static_cast(m_used_count - 1U); - - // wake one blocked allocator - true scheduler wait, no spin-yield - m_cv.NotifyOne_CS(); - return true; + return success; } // --------------------------------------------------------------------------- @@ -521,7 +548,7 @@ inline void BlockMemoryPool::BuildFreeList() // (lowest address), giving ascending allocation order. for (size_t i = m_capacity; i-- > 0U; ) { - MemoryBlock *blk = reinterpret_cast(m_storage + (i * m_block_size)); + MemoryBlock *const blk = reinterpret_cast(m_storage + (i * m_block_size)); blk->next = m_free_list; m_free_list = blk; @@ -532,7 +559,7 @@ inline void *BlockMemoryPool::PopFreeList() { STK_ASSERT(m_used_count < m_capacity); - MemoryBlock *blk = m_free_list; + MemoryBlock *const blk = m_free_list; m_free_list = blk->next; m_used_count = static_cast(m_used_count + 1U); diff --git a/stk/include/stk.h b/stk/include/stk.h index 5f4f47c..7d34a51 100644 --- a/stk/include/stk.h +++ b/stk/include/stk.h @@ -102,8 +102,8 @@ final */ enum ERequest : uint8_t { - REQUEST_NONE = 0, //!< No pending requests. - REQUEST_ADD_TASK = (1 << 0) //!< An AddTask() request is pending from a running task (KERNEL_DYNAMIC only). + REQ_NONE = 0, //!< No pending requests. + REQ_ADD_TASK = (1 << 0) //!< An AddTask() request is pending from a running task (KERNEL_DYNAMIC only). }; /*! \class KernelTask @@ -153,8 +153,10 @@ final m_srt(), m_hrt(), m_rt_weight() { // bind to wait object - if (IsSyncMode()) + if __stk_constexpr_cpp17 (IsSyncMode()) + { m_wait_obj->m_task = this; + } } /*! \brief Get bound user task. @@ -163,9 +165,9 @@ final ITask *GetUserTask() override { return m_user; } /*! \brief Get stack descriptor for this task slot. - \return Pointer to the Stack (SP register value and access mode flags). + \return Stack info (SP register value and access mode flags). */ - Stack *GetUserStack() override { return &m_stack; } + Stack GetUserStack() const { return m_stack;} /*! \brief Check whether this slot is bound to a user task. \return \c true if a user task is assigned (m_user != NULL); \c false if the slot is free. @@ -199,8 +201,10 @@ final */ void SetCurrentWeight(Weight weight) override { - if (TStrategy::WEIGHT_API) + if __stk_constexpr_cpp17 (TStrategy::WEIGHT_API) + { m_rt_weight[0] = weight; + } } /*! \brief Get static scheduling weight from the user task. @@ -208,13 +212,22 @@ final */ Weight GetWeight() const override { - if (TStrategy::PRIORITY_INHERITANCE_API) + if __stk_constexpr_cpp17 (TStrategy::PRIORITY_INHERITANCE_API) { if (m_rt_weight[0] != NO_WEIGHT) + { return m_rt_weight[0]; + } + } + + if __stk_constexpr_cpp17 (TStrategy::WEIGHT_API) + { + return m_user->GetWeight(); + } + else + { + return DEFAULT_WEIGHT; } - - return (TStrategy::WEIGHT_API ? m_user->GetWeight() : DEFAULT_WEIGHT); } /*! \brief Get current (run-time) scheduling weight. @@ -224,7 +237,14 @@ final */ Weight GetCurrentWeight() const override { - return (TStrategy::WEIGHT_API ? m_rt_weight[0] : DEFAULT_WEIGHT); + if __stk_constexpr_cpp17 (TStrategy::WEIGHT_API) + { + return m_rt_weight[0]; + } + else + { + return DEFAULT_WEIGHT; + } } /*! \brief Get HRT scheduling periodicity. @@ -234,8 +254,15 @@ final Timeout GetHrtPeriodicity() const override { STK_ASSERT(IsHrtMode()); - - return (IsHrtMode() ? m_hrt[0].periodicity : 0); + + if __stk_constexpr_cpp17 (IsHrtMode()) + { + return m_hrt[0].periodicity; + } + else + { + return 0; + } } /*! \brief Get absolute HRT deadline (ticks elapsed since task was activated). @@ -247,7 +274,14 @@ final { STK_ASSERT(IsHrtMode()); - return (IsHrtMode() ? m_hrt[0].deadline : 0); + if __stk_constexpr_cpp17 (IsHrtMode()) + { + return m_hrt[0].deadline; + } + else + { + return 0; + } } /*! \brief Get remaining HRT deadline (ticks left before the deadline expires). @@ -260,7 +294,14 @@ final STK_ASSERT(IsHrtMode()); STK_ASSERT(!IsSleeping()); - return (IsHrtMode() ? (m_hrt[0].deadline - m_hrt[0].duration) : 0); + if __stk_constexpr_cpp17 (IsHrtMode()) + { + return (m_hrt[0].deadline - m_hrt[0].duration); + } + else + { + return 0; + } } Timeout GetSleepTicks(Timeout sleep_ticks) @@ -268,7 +309,7 @@ final // note: task sleep time is negative Timeout task_sleep = Max(NO_WAIT, -m_time_sleep); - if (IsSyncMode()) + if __stk_constexpr_cpp17 (IsSyncMode()) { // likely task is sleeping during sync operation (see Wait) if (m_wait_obj->IsWaiting()) @@ -278,7 +319,9 @@ final // we shall account for only valid time (when task is waiting during sync operation) if (task_sleep > NO_WAIT) + { sleep_ticks = Min(sleep_ticks, task_sleep); + } } else { @@ -298,8 +341,7 @@ final /*! \brief Destructor. \note MISRA deviation: [STK-DEV-005] Rule 10-3-2. */ - ~KernelTask() - {} + STK_VIRT_DTOR ~KernelTask() = default; /*! \class SrtInfo \brief Per-task soft real-time (SRT) metadata. @@ -365,8 +407,7 @@ final /*! \brief Destructor. \note MISRA deviation: [STK-DEV-005] Rule 10-3-2. */ - ~WaitObject() - {} + STK_VIRT_DTOR ~WaitObject() = default; /*! \class WaitRequest \brief Payload stored in the sync object's kernel-side list entry while a task is waiting. @@ -419,12 +460,17 @@ final */ bool Tick(Timeout elapsed_ticks) override { - if ((m_time_wait != WAIT_INFINITE) && !m_timeout) + if (m_time_wait != WAIT_INFINITE) { - m_time_wait -= elapsed_ticks; + if (!m_timeout) + { + m_time_wait -= elapsed_ticks; - if (m_time_wait <= 0) - m_timeout = true; + if (m_time_wait <= 0) + { + m_timeout = true; + } + } } return !m_timeout; @@ -461,7 +507,7 @@ final void Bind(TPlatform *platform, ITask *user_task) { // set access mode for this stack - m_stack.mode = user_task->GetAccessMode(); + m_stack.access_mode = user_task->GetAccessMode(); // set task id for tracking purpose #if STK_NEED_TASK_ID @@ -478,8 +524,10 @@ final m_user = user_task; // initialize current weight to NO_WEIGHT for priority inheritance mechanism - if (TStrategy::PRIORITY_INHERITANCE_API) + if __stk_constexpr_cpp17 (TStrategy::PRIORITY_INHERITANCE_API) + { SetCurrentWeight(NO_WEIGHT); + } } /*! \brief Reset this slot to the free (unbound) state, clearing all scheduling metadata. @@ -487,7 +535,7 @@ final */ void Unbind() { - if (IsSyncMode()) + if __stk_constexpr_cpp17 (IsSyncMode()) { // should be freed from waiting on task exit STK_ASSERT(!m_wait_obj->IsWaiting()); @@ -498,10 +546,14 @@ final m_state = STATE_NONE; m_time_sleep = 0; - if (IsHrtMode()) + if __stk_constexpr_cpp17 (IsHrtMode()) + { m_hrt[0].Clear(); + } else + { m_srt->Clear(); + } } /*! \brief Schedule the removal of the task from the kernel on next tick. @@ -512,8 +564,10 @@ final ScheduleSleep(WAIT_INFINITE); // mark it as done HRT task - if (IsHrtMode()) + if __stk_constexpr_cpp17 (IsHrtMode()) + { HrtOnWorkCompleted(); + } // mark it as pending for removal m_state |= STATE_REMOVE_PENDING; @@ -528,10 +582,10 @@ final */ bool IsMemoryOfSP(Word SP) const { - const Word *const start = m_user->GetStack(); - const Word *const end = start + m_user->GetStackSize(); + const Word start = hw::PtrToWord(m_user->GetStack()); + const Word end = start + (m_user->GetStackSize() * sizeof(Word)); - return (SP >= hw::PtrToWord(start)) && (SP <= hw::PtrToWord(end)); + return (SP >= start) && (SP <= end); } /*! \brief Initialize task with HRT info. @@ -552,7 +606,9 @@ final m_hrt[0].deadline = deadline_tc; if (start_delay_tc > 0) + { ScheduleSleep(start_delay_tc); + } } /*! \brief Called when task is switched into the scheduling process. @@ -570,9 +626,11 @@ final STK_ASSERT(duration >= 0); - Timeout sleep = m_hrt[0].periodicity - duration; + const Timeout sleep = m_hrt[0].periodicity - duration; if (sleep > 0) + { ScheduleSleep(sleep); + } m_hrt[0].duration = 0; m_hrt[0].done = false; @@ -624,10 +682,12 @@ final STK_ASSERT(ticks > 0); // set state first as kernel checks it when task IsSleeping - if (TStrategy::SLEEP_EVENT_API) + if __stk_constexpr_cpp17 (TStrategy::SLEEP_EVENT_API) { if (!IsSleeping()) + { m_state |= STATE_SLEEP_PENDING; + } } m_time_sleep = -ticks; @@ -643,6 +703,11 @@ final __stk_relax_cpu(); } } + + /*! \brief Get pointer to user Stack. + \return Pointer to the Stack (SP register value and access mode flags). + */ + Stack *GetUserStackPtr() { return &m_stack; } ITask *m_user; //!< Bound user task, or \c NULL when slot is free. Stack m_stack; //!< Stack descriptor (SP register value + access mode + optional tid). @@ -696,7 +761,7 @@ final STK_ASSERT(!hw::IsInsideISR()); STK_ASSERT(ticks >= 0); - if (!IsHrtMode()) + if __stk_constexpr_cpp17 (!IsHrtMode()) { m_kernel->m_platform.Sleep(ticks); } @@ -711,7 +776,7 @@ final { STK_ASSERT(!hw::IsInsideISR()); - if (!IsHrtMode()) + if __stk_constexpr_cpp17 (!IsHrtMode()) { return m_kernel->m_platform.SleepUntil(timestamp); } @@ -725,7 +790,7 @@ final void SleepCancel(TId task_id) override { - if (!IsHrtMode()) + if __stk_constexpr_cpp17 (!IsHrtMode()) { m_kernel->OnTaskSleepCancel(task_id); } @@ -740,7 +805,7 @@ final IWaitObject *Wait(ISyncObject *sobj, IMutex *mutex, Timeout ticks) override { - if (IsSyncMode()) + if __stk_constexpr_cpp17 (IsSyncMode()) { return m_kernel->m_platform.Wait(sobj, mutex, ticks); } @@ -753,7 +818,7 @@ final Timeout Suspend() override { - if (IsTicklessMode()) + if __stk_constexpr_cpp17 (IsTicklessMode()) { return m_kernel->m_platform.Suspend(); } @@ -766,7 +831,7 @@ final void Resume(Timeout elapsed_ticks) override { - if (IsTicklessMode()) + if __stk_constexpr_cpp17 (IsTicklessMode()) { return m_kernel->m_platform.Resume(elapsed_ticks); } @@ -778,14 +843,18 @@ final void InheritWeight(TId tid, Weight weight) override { - if (TStrategy::PRIORITY_INHERITANCE_API) + if __stk_constexpr_cpp17 (TStrategy::PRIORITY_INHERITANCE_API) + { m_kernel->OnInheritWeight(tid, weight); + } } void RestoreWeight(TId tid, ISyncObject *sobj) override { - if (TStrategy::PRIORITY_INHERITANCE_API) + if __stk_constexpr_cpp17 (TStrategy::PRIORITY_INHERITANCE_API) + { m_kernel->OnRestoreWeight(tid, sobj); + } } private: @@ -798,8 +867,7 @@ final /*! \brief Destructor. \note MISRA deviation: [STK-DEV-005] Rule 10-3-2. */ - ~KernelService() - {} + STK_VIRT_DTOR ~KernelService() = default; /*! \brief Initialize instance. \note When call completes Singleton will start referencing this @@ -827,17 +895,17 @@ final public: /*! \brief Maximum number of concurrently registered tasks. Fixed at compile time. Exceeding this limit in AddTask() triggers a compile-time assert (TASKS_MAX > 0) and a runtime STK_ASSERT. */ - static constexpr uint32_t TASKS_MAX = TSize; + static constexpr size_t TASKS_MAX = TSize; /*! \brief Construct the kernel with all storage zero-initialized and the request flag set to ~0 - (indicating uninitialized state; cleared to REQUEST_NONE by Initialize()). + (indicating uninitialized state; cleared to REQ_NONE by Initialize()). \note In debug builds also verifies that TPlatform derives from IPlatform and TStrategy from ITaskSwitchStrategy. \note If TMode includes KERNEL_TICKLESS, a compile-time assertion fires unless STK_TICKLESS_IDLE is defined to 1 in stk_config.h. */ explicit Kernel() : m_platform(), m_strategy(), m_task_now(nullptr), m_task_storage(), m_sleep_trap(), - m_exit_trap(), m_fsm_state(FSM_STATE_NONE), m_request(REQUEST_NONE), m_state(STATE_INACTIVE) + m_exit_trap(), m_fsm_state(FSM_STATE_NONE), m_request(REQ_NONE), m_kstate(KSTATE_INACTIVE) { #ifdef _DEBUG // TPlatform must inherit IPlatform @@ -858,8 +926,7 @@ final /*! \brief Destructor. \note MISRA deviation: [STK-DEV-005] Rule 10-3-2. */ - ~Kernel() - {} + STK_VIRT_DTOR ~Kernel() = default; /*! \brief Initialize kernel. \param[in] resolution_us: Resolution of the system tick (SysTick) timer in microseconds. @@ -879,14 +946,24 @@ final // reinitialize key state variables m_task_now = nullptr; m_fsm_state = FSM_STATE_NONE; - m_request = REQUEST_NONE; + m_request = REQ_NONE; + + // exit trap is required only for KERNEL_DYNAMIC mode + Stack *exit_trap = nullptr; + if __stk_constexpr_cpp17 (IsDynamicMode()) + { + exit_trap = &m_exit_trap[0].stack; + } + else + { + STK_UNUSED(exit_trap); + } m_service.Initialize(this); - - m_platform.Initialize(this, &m_service, resolution_us, (IsDynamicMode() ? &m_exit_trap[0].stack : nullptr)); + m_platform.Initialize(this, &m_service, resolution_us, exit_trap); // now ready to Start() - m_state = STATE_READY; + m_kstate = KSTATE_READY; } /*! \brief Register task for a soft real-time (SRT) scheduling. @@ -899,7 +976,7 @@ final */ __stk_attr_noinline void AddTask(ITask *user_task) override { - if (!IsHrtMode()) + if __stk_constexpr_cpp17 (!IsHrtMode()) { STK_ASSERT(user_task != nullptr); STK_ASSERT(IsInitialized()); @@ -908,7 +985,7 @@ final // kernel processes this request if (IsStarted()) { - if (IsDynamicMode()) + if __stk_constexpr_cpp17 (IsDynamicMode()) { RequestAddTask(user_task); } @@ -939,7 +1016,7 @@ final __stk_attr_noinline void AddTask(ITask *user_task, Timeout periodicity_tc, Timeout deadline_tc, Timeout start_delay_tc) override { - if (IsHrtMode()) + if __stk_constexpr_cpp17 (IsHrtMode()) { STK_ASSERT(user_task != nullptr); STK_ASSERT(IsInitialized()); @@ -963,14 +1040,16 @@ final */ __stk_attr_noinline void RemoveTask(ITask *user_task) override { - if (IsDynamicMode()) + if __stk_constexpr_cpp17 (IsDynamicMode()) { STK_ASSERT(user_task != nullptr); STK_ASSERT(!IsStarted()); - KernelTask *task = FindTaskByUserTask(user_task); + KernelTask *const task = FindTaskByUserTask(user_task); if (task != nullptr) + { RemoveTask(task); + } } else { @@ -986,16 +1065,18 @@ final */ __stk_attr_noinline void ScheduleTaskRemoval(ITask *user_task) override { - if (IsDynamicMode()) + if __stk_constexpr_cpp17 (IsDynamicMode()) { STK_ASSERT(user_task != nullptr); STK_ASSERT(IsStarted()); - hw::CriticalSection::ScopedLock cs_; + const hw::CriticalSection::ScopedLock cs_; - KernelTask *task = FindTaskByUserTask(user_task); + KernelTask *const task = FindTaskByUserTask(user_task); if (task != nullptr) + { task->ScheduleRemoval(); + } } else { @@ -1015,20 +1096,20 @@ final STK_ASSERT(user_task != nullptr); bool self = false; - KernelTask *task = nullptr; // avoid race with OnTick { - hw::CriticalSection::ScopedLock cs_; + const hw::CriticalSection::ScopedLock cs_; - task = FindTaskByUserTask(user_task); + KernelTask *const task = FindTaskByUserTask(user_task); STK_ASSERT(task != nullptr); // only suspend if the task is currently awake: if it is already sleeping // (e.g. blocked on a mutex or timed Sleep), do not overwrite m_time_sleep, // that would corrupt the original sleep state and, for sync-object waits, // would interfere with WaitObject::Tick() - if ((suspended = !task->IsSleeping()) == true) + suspended = !task->IsSleeping(); + if (suspended == true) { task->ScheduleSleep(WAIT_INFINITE); @@ -1039,7 +1120,9 @@ final // note: we do not spin long here, kernel will switch this task out from scheduling on the next tick if (self) - task->BusyWaitWhileSleeping(); + { + m_task_now->BusyWaitWhileSleeping(); + } } /*! \brief Resume task. @@ -1050,54 +1133,60 @@ final STK_ASSERT(user_task != nullptr); // avoid race with OnTick - hw::CriticalSection::ScopedLock cs_; + const hw::CriticalSection::ScopedLock cs_; - KernelTask *task = FindTaskByUserTask(user_task); + KernelTask *const task = FindTaskByUserTask(user_task); STK_ASSERT(task != nullptr); if (task->IsSleeping()) + { task->Wake(); + } } - /*! \brief Enumerate kernel tasks. - \param[in,out] user_tasks: Pointer to the array for IKernelTask pointers. - \param[in] max_size: Max size of the provided array. - \return Number of tasks in the array. - */ - size_t EnumerateKernelTasks(IKernelTask **tasks, const size_t max_size) override + /*! \brief Enumerate kernel tasks. + \param[in] tasks: Reference to the ArrayView of IKernelTask pointers. + \return Number of tasks in the array. + */ + size_t EnumerateKernelTasks(ArrayView tasks) override { size_t count = 0U; + const size_t limit = Min(tasks.GetSize(), static_cast(TASKS_MAX)); // avoid race with OnTick - hw::CriticalSection::ScopedLock cs_; + const hw::CriticalSection::ScopedLock cs_; - for (uint32_t i = 0U; i < Min(max_size, static_cast(TASKS_MAX)); ++i) + for (size_t i = 0U; i < limit; ++i) { - KernelTask *task = &m_task_storage[i]; + KernelTask *const task = &m_task_storage[i]; if (task->IsBusy()) - tasks[count++] = task; + { + tasks[count++] = task; + } } return count; } - - /*! \brief Enumerate tasks. - \param[in,out] user_tasks: Pointer to the array for ITask pointers. - \param[in] max_size: Max size of the provided array. + + /*! \brief Enumerate user tasks. + \param[in] user_tasks: Reference to the ArrayView of ITask pointers. \return Number of tasks in the array. */ - size_t EnumerateTasks(ITask **user_tasks, const size_t max_size) override + size_t EnumerateTasks(ArrayView user_tasks) override { size_t count = 0U; + const size_t limit = Min(user_tasks.GetSize(), static_cast(TASKS_MAX)); // avoid race with OnTick - hw::CriticalSection::ScopedLock cs_; + const hw::CriticalSection::ScopedLock cs_; - for (uint32_t i = 0U; i < Min(max_size, static_cast(TASKS_MAX)); ++i) + for (size_t i = 0U; i < limit; ++i) { - KernelTask *task = &m_task_storage[i]; + KernelTask *const task = &m_task_storage[i]; if (task->IsBusy()) + { user_tasks[count++] = task->GetUserTask(); + } } return count; @@ -1121,11 +1210,13 @@ final // start tracing #if STK_SEGGER_SYSVIEW SEGGER_SYSVIEW_Start(); - for (uint32_t i = 0U; i < TASKS_MAX; ++i) + for (size_t i = 0U; i < TASKS_MAX; ++i) { KernelTask *task = &m_task_storage[i]; if (task->IsBusy()) + { SendTaskTraceInfo(task); + } } #endif @@ -1153,7 +1244,7 @@ final /*! \brief Get kernel state. */ - EState GetState() const override { return m_state; } + EKernelState GetState() const override { return m_kstate; } protected: /*! \enum EFsmState @@ -1208,26 +1299,26 @@ final SleepTrapStack &sleep = m_sleep_trap[0]; SleepTrapStackMemory wrapper(&sleep.memory); - sleep.stack.mode = ACCESS_PRIVILEGED; + sleep.stack.access_mode = ACCESS_PRIVILEGED; #if STK_NEED_TASK_ID sleep.stack.tid = SYS_TASK_ID_SLEEP; #endif - m_platform.InitStack(STACK_SLEEP_TRAP, &sleep.stack, &wrapper, nullptr); + STK_UNUSED(m_platform.InitStack(STACK_SLEEP_TRAP, &sleep.stack, &wrapper, nullptr)); } // init stack for an Exit trap - if (IsDynamicMode()) + if __stk_constexpr_cpp17 (IsDynamicMode()) { ExitTrapStack &exit = m_exit_trap[0]; ExitTrapStackMemory wrapper(&exit.memory); - exit.stack.mode = ACCESS_PRIVILEGED; + exit.stack.access_mode = ACCESS_PRIVILEGED; #if STK_NEED_TASK_ID exit.stack.tid = SYS_TASK_ID_EXIT; #endif - m_platform.InitStack(STACK_EXIT_TRAP, &exit.stack, &wrapper, nullptr); + STK_UNUSED(m_platform.InitStack(STACK_EXIT_TRAP, &exit.stack, &wrapper, nullptr)); } } @@ -1239,9 +1330,9 @@ final { // look for a free kernel task KernelTask *new_task = nullptr; - for (uint32_t i = 0U; i < TASKS_MAX; ++i) + for (size_t i = 0U; i < TASKS_MAX; ++i) { - KernelTask *task = &m_task_storage[i]; + KernelTask *const task = &m_task_storage[i]; if (task->IsBusy()) { // avoid task collision @@ -1258,6 +1349,10 @@ final break; // break if assertions are inactive and do not try to validate collision with existing tasks #endif } + else + { + // noop, continue to the next slot + } } // if nullptr - exceeded max supported kernel task count, application design failure @@ -1275,7 +1370,7 @@ final { #if STK_SEGGER_SYSVIEW // start tracing new task - SEGGER_SYSVIEW_OnTaskCreate(task->GetUserStack()->tid); + SEGGER_SYSVIEW_OnTaskCreate(task->GetUserStackPtr()->tid); if (IsStarted()) SendTaskTraceInfo(task); #endif @@ -1288,7 +1383,7 @@ final */ void AllocateAndAddNewTask(ITask *user_task) { - KernelTask *task = AllocateNewTask(user_task); + KernelTask *const task = AllocateNewTask(user_task); STK_ASSERT(task != nullptr); AddKernelTask(task); @@ -1303,7 +1398,7 @@ final */ void HrtAllocateAndAddNewTask(ITask *user_task, Timeout periodicity_tc, Timeout deadline_tc, Timeout start_delay_tc) { - KernelTask *task = AllocateNewTask(user_task); + KernelTask *const task = AllocateNewTask(user_task); STK_ASSERT(task != nullptr); task->HrtInit(periodicity_tc, deadline_tc, start_delay_tc); @@ -1315,11 +1410,9 @@ final \note Must be called by the task process only! \param[in] user_task: User task to add. */ - __stk_attr_noinline void RequestAddTask(ITask *user_task) + __stk_attr_noinline void RequestAddTask(ITask *const user_task) { - STK_ASSERT(IsDynamicMode()); - - KernelTask *caller = FindTaskBySP(m_platform.GetCallerSP()); + KernelTask *const caller = FindTaskBySP(m_platform.GetCallerSP()); STK_ASSERT(caller != nullptr); typename KernelTask::AddTaskRequest req = { .user_task = user_task }; @@ -1330,7 +1423,9 @@ final // switch out and wait for completion (due to context switch request could be processed here) if (caller->m_srt[0].add_task_req != nullptr) + { m_service.SwitchToNext(); + } STK_ASSERT(caller->m_srt[0].add_task_req == nullptr); } @@ -1341,14 +1436,19 @@ final */ __stk_attr_noinline KernelTask *FindTaskByUserTask(const ITask *user_task) { - for (uint32_t i = 0U; i < TASKS_MAX; ++i) + KernelTask *found_task = nullptr; + + for (size_t i = 0U; i < TASKS_MAX; ++i) { - KernelTask *task = &m_task_storage[i]; + KernelTask *const task = &m_task_storage[i]; if (task->GetUserTask() == user_task) - return task; + { + found_task = task; + break; + } } - return nullptr; + return found_task; } /*! \brief Find kernel task by the bound Stack instance. @@ -1357,40 +1457,59 @@ final */ KernelTask *FindTaskByStack(const Stack *stack) { - for (uint32_t i = 0U; i < TASKS_MAX; ++i) + KernelTask *found_task = nullptr; + + for (size_t i = 0U; i < TASKS_MAX; ++i) { - KernelTask *task = &m_task_storage[i]; - if (task->GetUserStack() == stack) - return task; + KernelTask *const task = &m_task_storage[i]; + if (task->GetUserStackPtr() == stack) + { + found_task = task; + break; + } } - return nullptr; + return found_task; } /*! \brief Find kernel task for a Stack Pointer (SP). \param[in] SP: Stack pointer. \return Kernel task. - */ + */ __stk_attr_noinline KernelTask *FindTaskBySP(Word SP) { STK_ASSERT(m_task_now != nullptr); + + KernelTask *found_task = nullptr; if (m_task_now->IsMemoryOfSP(SP)) - return m_task_now; - - for (uint32_t i = 0U; i < TASKS_MAX; ++i) { - KernelTask *task = &m_task_storage[i]; + found_task = m_task_now; + } + else + { + for (size_t i = 0U; i < TASKS_MAX; ++i) + { + KernelTask *const task = &m_task_storage[i]; - // skip finished tasks (applicable only for KERNEL_DYNAMIC mode) - if (IsDynamicMode() && !task->IsBusy()) - continue; + // skip finished tasks (applicable only for KERNEL_DYNAMIC mode) + if __stk_constexpr_cpp17 (IsDynamicMode()) + { + if (!task->IsBusy()) + { + continue; + } + } - if (task->IsMemoryOfSP(SP)) - return task; + if (task->IsMemoryOfSP(SP)) + { + found_task = task; + break; + } + } } - return nullptr; + return found_task; } /*! \brief Remove kernel task. @@ -1402,7 +1521,7 @@ final STK_ASSERT(task != nullptr); #if STK_SEGGER_SYSVIEW - SEGGER_SYSVIEW_OnTaskTerminate(task->GetUserStack()->tid); + SEGGER_SYSVIEW_OnTaskTerminate(task->GetUserStackPtr()->tid); #endif // notify task about pending exit @@ -1427,11 +1546,11 @@ final STK_ASSERT(m_strategy.GetSize() != 0); // iterate tasks and generate OnTaskSleep for a strategy for all initially sleeping tasks - if (TStrategy::SLEEP_EVENT_API) + if __stk_constexpr_cpp17 (TStrategy::SLEEP_EVENT_API) { - for (uint32_t i = 0U; i < TASKS_MAX; ++i) + for (size_t i = 0U; i < TASKS_MAX; ++i) { - KernelTask *task = &m_task_storage[i]; + KernelTask *const task = &m_task_storage[i]; if (task->IsSleeping()) { @@ -1460,10 +1579,12 @@ final { m_task_now = next; - active = next->GetUserStack(); + active = next->GetUserStackPtr(); - if (IsHrtMode()) + if __stk_constexpr_cpp17 (IsHrtMode()) + { next->HrtOnSwitchedIn(); + } } else if (m_fsm_state == FSM_STATE_SLEEPING) @@ -1474,10 +1595,15 @@ final active = &m_sleep_trap[0].stack; } + else + { + // unexpected state + STK_KERNEL_PANIC(KERNEL_PANIC_BAD_STATE); + } } // is in running state - m_state = STATE_RUNNING; + m_kstate = KSTATE_RUNNING; #if STK_SEGGER_SYSVIEW SEGGER_SYSVIEW_OnTaskStartExec(m_task_now->tid); @@ -1491,12 +1617,12 @@ final */ __stk_attr_noinline void OnStop() override { - if (IsDynamicMode()) + if __stk_constexpr_cpp17 (IsDynamicMode()) { m_fsm_state = FSM_STATE_NONE; // is in stopped state, i.e. is ready to Start() again - m_state = STATE_READY; + m_kstate = KSTATE_READY; } } @@ -1550,18 +1676,22 @@ final void OnTaskSleep(Word caller_SP, Timeout ticks) override { - KernelTask *task = FindTaskBySP(caller_SP); + KernelTask *const task = FindTaskBySP(caller_SP); STK_ASSERT(task != nullptr); // make change to HRT state and sleep time atomic { - hw::CriticalSection::ScopedLock cs_; + const hw::CriticalSection::ScopedLock cs_; - if (IsHrtMode()) + if __stk_constexpr_cpp17 (IsHrtMode()) + { task->HrtOnWorkCompleted(); + } if (ticks > 0) + { task->ScheduleSleep(ticks); + } } // note: we do not spin long here, kernel will switch this task out from scheduling on the next tick @@ -1570,24 +1700,26 @@ final bool OnTaskSleepUntil(Word caller_SP, Ticks timestamp) override { - STK_ASSERT(!IsHrtMode()); - - KernelTask *task = FindTaskBySP(caller_SP); + KernelTask *const task = FindTaskBySP(caller_SP); STK_ASSERT(task != nullptr); bool result = true; // make change to HRT state and sleep time atomic { - hw::CriticalSection::ScopedLock cs_; + const hw::CriticalSection::ScopedLock cs_; // calculate signed delta (handles wrap-around correctly) const Ticks delta = timestamp - m_service.m_ticks; if (delta > 0) + { task->ScheduleSleep(static_cast(Min(delta, static_cast(INT32_MAX)))); + } else + { result = false; // deadline already hit or passed + } } // note: we do not spin long here, kernel will switch this task out from scheduling on the next tick @@ -1597,21 +1729,23 @@ final void OnTaskSleepCancel(TId task_id) { - KernelTask *task = FindTaskByUserTask(GetUserTaskFromTid(task_id)); + KernelTask *const task = FindTaskByUserTask(GetUserTaskFromTid(task_id)); if (task != nullptr) { - hw::CriticalSection::ScopedLock cs_; + const hw::CriticalSection::ScopedLock cs_; if (task->IsSleeping()) + { task->Wake(); + } } } void OnTaskExit(Stack *stack) override { - if (IsDynamicMode()) + if __stk_constexpr_cpp17 (IsDynamicMode()) { - KernelTask *task = FindTaskByStack(stack); + KernelTask *const task = FindTaskByStack(stack); STK_ASSERT(task != nullptr); // notify kernel to execute removal @@ -1626,14 +1760,14 @@ final IWaitObject *OnTaskWait(Word caller_SP, ISyncObject *sync_obj, IMutex *mutex, Timeout timeout) override { - if (IsSyncMode()) + if __stk_constexpr_cpp17 (IsSyncMode()) { STK_ASSERT(timeout != 0); // API contract: caller must not be in ISR STK_ASSERT(sync_obj != nullptr); // API contract: ISyncObject instance must be provided STK_ASSERT(mutex != nullptr); // API contract: IMutex instance must be provided STK_ASSERT((sync_obj->GetHead() == nullptr) || (sync_obj->GetHead() == &m_sync_list[0])); - KernelTask *task = FindTaskBySP(caller_SP); + KernelTask *const task = FindTaskBySP(caller_SP); STK_ASSERT(task != nullptr); // configure waiting @@ -1641,7 +1775,9 @@ final // register ISyncObject if not yet if (sync_obj->GetHead() == nullptr) + { m_sync_list->LinkBack(sync_obj); + } // start sleeping infinitely, we rely on a Wake call via WaitObject task->ScheduleSleep(WAIT_INFINITE); @@ -1666,7 +1802,7 @@ final TId OnGetTid(Word caller_SP) override { - KernelTask *task = FindTaskBySP(caller_SP); + KernelTask *const task = FindTaskBySP(caller_SP); STK_ASSERT(task != nullptr); return task->GetTid(); @@ -1674,14 +1810,27 @@ final void OnSuspend(bool suspended) override { + // toggle kernel state if (suspended) - m_state = ((m_state == STATE_RUNNING) ? STATE_SUSPENDED : m_state); + { + if (m_kstate == KSTATE_RUNNING) + { + m_kstate = KSTATE_SUSPENDED; + } + } else - m_state = ((m_state == STATE_SUSPENDED) ? STATE_RUNNING : m_state); + { + if (m_kstate == KSTATE_SUSPENDED) + { + m_kstate = KSTATE_RUNNING; + } + } // force yield for a currently active task if (!m_task_now->IsSleeping()) + { m_task_now->ScheduleSleep(YIELD_TICKS); + } } void OnInheritWeight(TId tid, Weight weight) @@ -1691,10 +1840,10 @@ final if (weight != NO_WEIGHT) { - KernelTask *task = FindTaskByUserTask(GetUserTaskFromTid(tid)); + KernelTask *const task = FindTaskByUserTask(GetUserTaskFromTid(tid)); STK_ASSERT(task != nullptr); - Weight prev_weight = task->GetWeight(); + const Weight prev_weight = task->GetWeight(); if (prev_weight < weight) { @@ -1709,10 +1858,10 @@ final STK_ASSERT(tid != TID_NONE); STK_ASSERT(TStrategy::WEIGHT_API && TStrategy::PRIORITY_INHERITANCE_API); - KernelTask *task = FindTaskByUserTask(GetUserTaskFromTid(tid)); + KernelTask *const task = FindTaskByUserTask(GetUserTaskFromTid(tid)); STK_ASSERT(task != nullptr); - Weight prev_weight = task->GetWeight(); + const Weight prev_weight = task->GetWeight(); // restore to original or boost from wait objects task->SetCurrentWeight(sobj != nullptr ? sobj->FindWeightHigherThan(task->GetWeight()) : NO_WEIGHT); @@ -1725,14 +1874,19 @@ final Timeout UpdateTasks(const Timeout elapsed_ticks) { // sync objects are updated before UpdateTaskRequest which may add a new object (newly added object must become 1 tick older) - if (IsSyncMode()) + if __stk_constexpr_cpp17 (IsSyncMode()) + { UpdateSyncObjects(elapsed_ticks); + } - UpdateTaskRequest(); + if (m_request != REQ_NONE) + { + UpdateTaskRequest(); + } return UpdateTaskState(elapsed_ticks); } - + /*! \brief Update task state: process removals, advance sleep timers, and track HRT durations. \param[in] elapsed_ticks: Number of ticks elapsed since the previous call. Always 1 in non-tickless mode, may be >1 in tickless mode. @@ -1744,21 +1898,21 @@ final */ Timeout UpdateTaskState(const Timeout elapsed_ticks) { - Timeout sleep_ticks = (IsTicklessMode() ? STK_TICKLESS_TICKS_MAX : 1); + Timeout sleep_ticks = GetInitialSleepTicks(); - for (uint32_t i = 0U; i < TASKS_MAX; ++i) + for (size_t i = 0U; i < TASKS_MAX; ++i) { - KernelTask *task = &m_task_storage[i]; + KernelTask *const task = &m_task_storage[i]; if (task->IsSleeping()) { - if (IsDynamicMode()) + if __stk_constexpr_cpp17 (IsDynamicMode()) { // task is pending removal, wait until it is switched out if (task->IsPendingRemoval()) { if ((task != m_task_now) || - ((m_strategy.GetSize() == 1) && (m_fsm_state == FSM_STATE_SLEEPING))) + ((m_strategy.GetSize() == 1U) && (m_fsm_state == FSM_STATE_SLEEPING))) { RemoveTask(task); continue; @@ -1768,7 +1922,7 @@ final // deliver sleep event to strategy // note: only currently scheduled task can be pending to sleep - if (TStrategy::SLEEP_EVENT_API) + if __stk_constexpr_cpp17 (TStrategy::SLEEP_EVENT_API) { if ((task->m_state & KernelTask::STATE_SLEEP_PENDING) != 0U) { @@ -1783,41 +1937,52 @@ final task->m_time_sleep += elapsed_ticks; // deliver sleep event to strategy - if (TStrategy::SLEEP_EVENT_API) + if __stk_constexpr_cpp17 (TStrategy::SLEEP_EVENT_API) { // notify strategy that task woke up if (!task->IsSleeping()) + { m_strategy.OnTaskWake(task); + } } } else - if (IsHrtMode()) { - // in HRT mode we trace how long task spent in active state (doing some work) - if (task->IsBusy()) + if __stk_constexpr_cpp17 (IsHrtMode()) { - task->m_hrt[0].duration += elapsed_ticks; - - // check if deadline is missed (HRT failure) - if (task->HrtIsDeadlineMissed(task->m_hrt[0].duration)) + // in HRT mode we trace how long task spent in active state (doing some work) + if (task->IsBusy()) { - bool can_recover = false; - - // report deadline overrun to a strategy which supports overrun recovery - if (TStrategy::DEADLINE_MISSED_API) - can_recover = m_strategy.OnTaskDeadlineMissed(task); + task->m_hrt[0].duration += elapsed_ticks; - // report failure if it could not be recovered by a scheduling strategy - if (!can_recover) - task->HrtHardFailDeadline(&m_platform); + // check if deadline is missed (HRT failure) + if (task->HrtIsDeadlineMissed(task->m_hrt[0].duration)) + { + // report deadline overrun to a strategy which supports overrun recovery + if __stk_constexpr_cpp17 (TStrategy::DEADLINE_MISSED_API) + { + if (!m_strategy.OnTaskDeadlineMissed(task)) + { + // report failure if it could not be recovered by the scheduling strategy + task->HrtHardFailDeadline(&m_platform); + } + } + else + { + task->HrtHardFailDeadline(&m_platform); + } + } } } } // get the number ticks the driver has to keep CPU in Idle - if (IsTicklessMode() && (sleep_ticks > 1) && task->IsBusy()) + if __stk_constexpr_cpp17 (IsTicklessMode()) { - sleep_ticks = task->GetSleepTicks(sleep_ticks); + if ((sleep_ticks > 1) && task->IsBusy()) + { + sleep_ticks = task->GetSleepTicks(sleep_ticks); + } } } @@ -1828,18 +1993,16 @@ final */ void UpdateSyncObjects(const Timeout elapsed_ticks) { - STK_ASSERT(IsSyncMode()); - ISyncObject::ListEntryType *itr = m_sync_list->GetFirst(); while (itr != nullptr) { - ISyncObject::ListEntryType *next = itr->GetNext(); + ISyncObject::ListEntryType *const next = itr->GetNext(); - // MISRA 5-2-3 deviation: GetNext/GetFirst returns ISyncObject*, all objects in - // m_sync_list are ISyncObject instances - downcast is guaranteed safe if (!static_cast(itr)->Tick(elapsed_ticks)) + { m_sync_list->Unlink(itr); + } itr = next; } @@ -1849,22 +2012,19 @@ final */ void UpdateTaskRequest() { - if (m_request == REQUEST_NONE) - return; - // process AddTask requests coming from tasks (KERNEL_DYNAMIC mode only, KERNEL_HRT is // excluded as we assume that HRT tasks must be known to the kernel before a Start()) - if (IsDynamicMode() && !IsHrtMode()) + if __stk_constexpr_cpp17 (IsDynamicMode() && !IsHrtMode()) { // process serialized AddTask request made from another active task, requesting process // is currently waiting due to SwitchToNext() - if ((m_request & REQUEST_ADD_TASK) != 0U) + if ((m_request & REQ_ADD_TASK) != 0U) { - m_request &= ~REQUEST_ADD_TASK; + m_request &= ~REQ_ADD_TASK; - for (uint32_t i = 0U; i < TASKS_MAX; ++i) + for (size_t i = 0U; i < TASKS_MAX; ++i) { - KernelTask *task = &m_task_storage[i]; + KernelTask *const task = &m_task_storage[i]; if (task->m_srt[0].add_task_req != nullptr) { @@ -1877,45 +2037,45 @@ final } } } - + /*! \brief Fetch next event for the FSM. \param[out] next: Next kernel task to which Kernel can switch. \return FSM event. */ EFsmEvent FetchNextEvent(KernelTask *&next) { - EFsmEvent type = FSM_EVENT_EXIT; - KernelTask *itr = nullptr; + EFsmEvent type = FSM_EVENT_SLEEP; - // check if no tasks left in KERNEL_DYNAMIC mode and exit, if KERNEL_DYNAMIC is not - // set then 'is_empty' will always be false - bool is_empty = IsDynamicMode() && (m_strategy.GetSize() == 0U); + // try getting next task for scheduling + next = static_cast(m_strategy.GetNext()); - if (!is_empty) + // sleep-aware strategy returns nullptr if no active tasks available + if (next != nullptr) { - // MISRA 5-2-3 deviation: GetNext/GetFirst returns IKernelTask*, all objects in - // the strategy pool are KernelTask instances - downcast is guaranteed safe. - itr = static_cast(m_strategy.GetNext()); + // strategy must provide active-only task + STK_ASSERT(!next->IsSleeping()); - // sleep-aware strategy returns nullptr if no active tasks available, start sleeping - if (itr == nullptr) - { - type = FSM_EVENT_SLEEP; - } - else + // if was sleeping, process wake event first + type = (m_fsm_state == FSM_STATE_SLEEPING ? FSM_EVENT_WAKE : FSM_EVENT_SWITCH); + } + // start sleeping + else + { + if __stk_constexpr_cpp17 (IsDynamicMode()) { - // strategy must provide active-only task - STK_ASSERT(!itr->IsSleeping()); - - // if was sleeping, process wake event first - type = (m_fsm_state == FSM_STATE_SLEEPING ? FSM_EVENT_WAKE : FSM_EVENT_SWITCH); + // if nullptr is returned then either strategy has all tasks sleeping or none left, + // if KERNEL_DYNAMIC mode and no tasks left then exit from scheduling + if (m_strategy.GetSize() == 0U) + { + next = nullptr; + type = FSM_EVENT_EXIT; + } } } - next = itr; return type; } - + /*! \brief Get new FSM state. \param[out] next: Next kernel task to which Kernel can switch. \return FSM state. @@ -1936,34 +2096,37 @@ final */ bool UpdateFsmState(Stack *&idle, Stack *&active) { - KernelTask *now = m_task_now, *next = nullptr; + KernelTask *const now = m_task_now, *next = nullptr; bool switch_context = false; - EFsmState new_state = GetNewFsmState(next); + const EFsmState new_state = GetNewFsmState(next); switch (new_state) { case FSM_STATE_SWITCHING: switch_context = StateSwitch(now, next, idle, active); + m_fsm_state = new_state; break; case FSM_STATE_SLEEPING: switch_context = StateSleep(now, next, idle, active); + m_fsm_state = new_state; break; case FSM_STATE_WAKING: switch_context = StateWake(now, next, idle, active); + m_fsm_state = new_state; break; case FSM_STATE_EXITING: switch_context = StateExit(now, next, idle, active); + m_fsm_state = new_state; break; case FSM_STATE_NONE: - return switch_context; // valid intermittent non-persisting state: no-transition + break; // valid intermittent non-persisting state: no-transition case FSM_STATE_MAX: - default: // invalid state value + default: // invalid state value STK_KERNEL_PANIC(KERNEL_PANIC_BAD_STATE); break; } - m_fsm_state = new_state; return switch_context; } @@ -1978,39 +2141,43 @@ final { STK_ASSERT(now != nullptr); STK_ASSERT(next != nullptr); + + bool switch_context = false; - // do not switch context because task did not change - if (next == now) - return false; - - idle = now->GetUserStack(); - active = next->GetUserStack(); - - // if stack memory is exceeded these assertions will be hit - if (now->IsBusy()) + // if equal: do not switch context because task did not change + if (next != now) { - // current task could exit, thus we check it with IsBusy to avoid referencing nullptr returned by GetUserTask() - STK_ASSERT(now->GetUserTask()->GetStack()[0] == STK_STACK_MEMORY_FILLER); - } - STK_ASSERT(next->GetUserTask()->GetStack()[0] == STK_STACK_MEMORY_FILLER); + idle = now->GetUserStackPtr(); + active = next->GetUserStackPtr(); - m_task_now = next; + // if stack memory is exceeded these assertions will be hit + if (now->IsBusy()) + { + // current task could exit, thus we check it with IsBusy to avoid referencing nullptr returned by GetUserTask() + STK_ASSERT(now->GetUserTask()->GetStack()[0] == STK_STACK_MEMORY_FILLER); + } + STK_ASSERT(next->GetUserTask()->GetStack()[0] == STK_STACK_MEMORY_FILLER); - if ((IsHrtMode())) - { - if (now->m_hrt[0].done) + m_task_now = next; + + if __stk_constexpr_cpp17 (IsHrtMode()) { - now->HrtOnSwitchedOut(&m_platform); - next->HrtOnSwitchedIn(); + if (now->m_hrt[0].done) + { + now->HrtOnSwitchedOut(&m_platform); + next->HrtOnSwitchedIn(); + } } - } - #if STK_SEGGER_SYSVIEW - SEGGER_SYSVIEW_OnTaskStopReady(now->GetUserStack()->tid, TRACE_EVENT_SWITCH); - SEGGER_SYSVIEW_OnTaskStartReady(next->GetUserStack()->tid); - #endif + #if STK_SEGGER_SYSVIEW + SEGGER_SYSVIEW_OnTaskStopReady(now->GetUserStackPtr()->tid, TRACE_EVENT_SWITCH); + SEGGER_SYSVIEW_OnTaskStartReady(next->GetUserStackPtr()->tid); + #endif + + switch_context = true; + } - return true; // switch context + return switch_context; } /*! \brief Wakes up after sleeping. @@ -2027,7 +2194,7 @@ final STK_ASSERT(next != nullptr); idle = &m_sleep_trap[0].stack; - active = next->GetUserStack(); + active = next->GetUserStackPtr(); // if stack memory is exceeded these assertions will be hit STK_ASSERT(m_sleep_trap[0].memory[0] == STK_STACK_MEMORY_FILLER); @@ -2036,11 +2203,13 @@ final m_task_now = next; #if STK_SEGGER_SYSVIEW - SEGGER_SYSVIEW_OnTaskStartReady(next->GetUserStack()->tid); + SEGGER_SYSVIEW_OnTaskStartReady(next->GetUserStackPtr()->tid); #endif - if ((IsHrtMode())) + if __stk_constexpr_cpp17 (IsHrtMode()) + { next->HrtOnSwitchedIn(); + } return true; // switch context } @@ -2059,19 +2228,21 @@ final STK_ASSERT(now != nullptr); STK_ASSERT(m_sleep_trap[0].stack.SP != 0); - idle = now->GetUserStack(); + idle = now->GetUserStackPtr(); active = &m_sleep_trap[0].stack; m_task_now = static_cast(m_strategy.GetFirst()); #if STK_SEGGER_SYSVIEW - SEGGER_SYSVIEW_OnTaskStopReady(now->GetUserStack()->tid, TRACE_EVENT_SLEEP); + SEGGER_SYSVIEW_OnTaskStopReady(now->GetUserStackPtr()->tid, TRACE_EVENT_SLEEP); #endif - if (IsHrtMode()) + if __stk_constexpr_cpp17 (IsHrtMode()) { if (!now->IsPendingRemoval()) + { now->HrtOnSwitchedOut(&m_platform); + } } return true; // switch context @@ -2090,7 +2261,7 @@ final STK_UNUSED(now); STK_UNUSED(next); - if (IsDynamicMode()) + if __stk_constexpr_cpp17 (IsDynamicMode()) { // dynamic tasks are not supported if main processes's stack memory is not provided in Start() STK_ASSERT(m_exit_trap[0].stack.SP != 0); @@ -2114,16 +2285,16 @@ final /*! \brief Check whether Initialize() has been called and completed successfully. \return \c true if Initialize() was called, \c false otherwise. */ - bool IsInitialized() const { return (m_state != STATE_INACTIVE); } + bool IsInitialized() const { return (m_kstate != KSTATE_INACTIVE); } /*! \brief Signal the kernel to process a pending AddTask request on the next tick. - \note Sets the REQUEST_ADD_TASK bit in m_request and emits a full memory fence + \note Sets the REQ_ADD_TASK bit in m_request and emits a full memory fence so the ISR-side tick handler observes the flag without delay. */ void ScheduleAddTask() { - hw::CriticalSection::ScopedLock cs_; - m_request |= REQUEST_ADD_TASK; + const hw::CriticalSection::ScopedLock cs_; + m_request |= REQ_ADD_TASK; } #if STK_SEGGER_SYSVIEW @@ -2137,7 +2308,7 @@ final SEGGER_SYSVIEW_TASKINFO info = { - .TaskID = task->GetUserStack()->tid, + .TaskID = task->GetUserStackPtr()->tid, .sName = task->GetUserTask()->GetTraceName(), .Prio = 0, .StackBase = hw::PtrToWord(task->GetUserTask()->GetStack()), @@ -2148,14 +2319,14 @@ final #endif // Kernel modes: - static __stk_forceinline bool IsStaticMode() { return ((TMode & KERNEL_STATIC) != 0U); } - static __stk_forceinline bool IsDynamicMode() { return ((TMode & KERNEL_DYNAMIC) != 0U); } - static __stk_forceinline bool IsHrtMode() { return ((TMode & KERNEL_HRT) != 0U); } - static __stk_forceinline bool IsSyncMode() { return ((TMode & KERNEL_SYNC) != 0U); } - static __stk_forceinline bool IsTicklessMode() { return ((TMode & KERNEL_TICKLESS) != 0U); } + static constexpr bool IsStaticMode() { return ((TMode & KERNEL_STATIC) != 0U); } + static constexpr bool IsDynamicMode() { return ((TMode & KERNEL_DYNAMIC) != 0U); } + static constexpr bool IsHrtMode() { return ((TMode & KERNEL_HRT) != 0U); } + static constexpr bool IsSyncMode() { return ((TMode & KERNEL_SYNC) != 0U); } + static constexpr bool IsTicklessMode() { return ((TMode & KERNEL_TICKLESS) != 0U); } // If hit here: Kernel expects at least 1 task, e.g. N > 0 - STK_STATIC_ASSERT_N(TASKS_MAX, TASKS_MAX > 0U); + STK_STATIC_ASSERT_N(TASKS_MAX, TASKS_MAX != 0U); // If hit here: Kernel mode must be assigned. STK_STATIC_ASSERT_N(KERNEL_MODE_MUST_BE_SET, (TMode != 0U)); @@ -2229,7 +2400,7 @@ final ExitTrapStack m_exit_trap[STK_ALLOCATE_COUNT::Value]; //!< Exit trap: zero-size in KERNEL_STATIC mode; one entry in KERNEL_DYNAMIC mode. EFsmState m_fsm_state; //!< Current FSM state. Drives context-switch decision on every tick. volatile uint8_t m_request; //!< Bitmask of pending ERequest flags from running tasks. Written by tasks, read/cleared by UpdateTaskRequest() in tick context. - volatile EState m_state; //!< Current kernel state. + volatile EKernelState m_kstate; //!< Current kernel state. SyncObjectList m_sync_list[STK_ALLOCATE_COUNT::Value]; //!< List of active sync objects. Zero-size (no memory) if KERNEL_SYNC is not set. const EFsmState m_fsm[FSM_STATE_MAX][FSM_EVENT_MAX] = { diff --git a/stk/include/stk_arch.h b/stk/include/stk_arch.h index 3fad776..669a925 100644 --- a/stk/include/stk_arch.h +++ b/stk/include/stk_arch.h @@ -44,10 +44,22 @@ #define _STK_ARCH_DEFINED #endif +#ifndef STK_PANIC_HANDLER + /*! \brief Default panic handler: disable interrupts, record the id, + and spin in a tight loop — a defined, detectable safe state. + \note On a system with a watchdog enabled this will trigger a watchdog + reset after the watchdog period, which is the desired behaviour. + \note Replace with a platform-specific handler (e.g. one that writes a + fault log to non-volatile memory and calls NVIC_SystemReset()) by + defining STK_PANIC_HANDLER in stk_config.h. + */ + extern void STK_PANIC_HANDLER_DEFAULT(stk::EKernelPanicId id); + #define STK_PANIC_HANDLER(id) STK_PANIC_HANDLER_DEFAULT(id) +#endif + namespace stk { -/*! \def STK_KERNEL_PANIC - \brief Called when the kernel detects an unrecoverable internal fault. +/*! \brief Called when the kernel detects an unrecoverable internal fault. \note Unlike STK_ASSERT (which checks preconditions) this macro is reached only when a runtime invariant has been irreversibly violated — the kernel cannot continue operating correctly from this point. @@ -60,11 +72,11 @@ namespace stk { and must never return. A minimal safe default is provided below. \param[in] id: EKernelPanicId value identifying the fault. */ -#define STK_KERNEL_PANIC(id) \ - do { \ - __stk_debug_break(); /* debug aid */ \ - STK_PANIC_HANDLER(id); /* must not return */ \ - } while (0) +static __stk_forceinline void STK_KERNEL_PANIC(stk::EKernelPanicId id) +{ + __stk_debug_break(); // debug aid + STK_PANIC_HANDLER(id); // must not return +} /*! \namespace stk::hw \brief Hardware Abstraction Layer (HAL) for architecture-specific operations. @@ -91,7 +103,7 @@ namespace hw { \see WordToPtr */ template -static constexpr Word PtrToWord(T *ptr) noexcept +static constexpr Word PtrToWord(T *const ptr) noexcept { STK_STATIC_ASSERT(sizeof(Word) == sizeof(T *)); return reinterpret_cast(ptr); @@ -466,10 +478,11 @@ struct HiResClock */ static inline Ticks GetTimeUs() { - uint32_t freq = GetFrequency(); - STK_ASSERT(freq != 0); + const uint32_t freq = GetFrequency(); + STK_ASSERT(freq != 0U); - return ((freq != 0) ? static_cast((GetCycles() * 1000000ULL) / freq) : 0 ); + return ((freq != 0U) ? + static_cast((GetCycles() * 1000000ULL) / freq) : static_cast(0)); } }; @@ -489,17 +502,4 @@ static constexpr ITask *GetUserTaskFromTid(TId task_id) noexcept { return hw::Wo } // namespace stk -#ifndef STK_PANIC_HANDLER - /*! \brief Default panic handler: disable interrupts, record the id, - and spin in a tight loop — a defined, detectable safe state. - \note On a system with a watchdog enabled this will trigger a watchdog - reset after the watchdog period, which is the desired behaviour. - \note Replace with a platform-specific handler (e.g. one that writes a - fault log to non-volatile memory and calls NVIC_SystemReset()) by - defining STK_PANIC_HANDLER in stk_config.h. - */ - extern void STK_PANIC_HANDLER_DEFAULT(stk::EKernelPanicId id); - #define STK_PANIC_HANDLER(id) STK_PANIC_HANDLER_DEFAULT(id) -#endif - #endif /* STK_ARCH_H_ */ diff --git a/stk/include/stk_common.h b/stk/include/stk_common.h index 65a40ce..e65bfd9 100644 --- a/stk/include/stk_common.h +++ b/stk/include/stk_common.h @@ -51,16 +51,17 @@ enum EKernelMode : uint8_t */ enum EKernelPanicId : uint32_t { - KERNEL_PANIC_NONE = 0, //!< Panic is absent (no fault). - KERNEL_PANIC_SPINLOCK_DEADLOCK = 1, //!< Spin-lock timeout expired: lock owner never released. - KERNEL_PANIC_STACK_CORRUPT = 2, //!< Stack integrity check failed. - KERNEL_PANIC_ASSERT = 3, //!< Internal assertion failed (maps from STK_ASSERT). - KERNEL_PANIC_HRT_HARD_FAULT = 4, //!< Kernel running in KERNEL_HRT mode reported deadline failure of the task. - KERNEL_PANIC_CPU_EXCEPTION = 5, //!< CPU reported an exception and halted execution. - KERNEL_PANIC_CS_NESTING_OVERFLOW = 6, //!< Critical section nesting limit exceeded: violation of STK_CRITICAL_SECTION_NESTINGS_MAX. - KERNEL_PANIC_UNKNOWN_SVC = 7, //!< Unknown service command received by SVC handler. - KERNEL_PANIC_BAD_STATE = 8, //!< Kernel entered unexpected (bad) state. - KERNEL_PANIC_BAD_MODE = 9 //!< Kernel is in bad/unsupported mode for the current operation. + KERNEL_PANIC_NONE = 0U, //!< Panic is absent (no fault). + KERNEL_PANIC_SPINLOCK_DEADLOCK = 1U, //!< Spin-lock timeout expired: lock owner never released. + KERNEL_PANIC_STACK_CORRUPT = 2U, //!< Stack integrity check failed. + KERNEL_PANIC_ASSERT = 3U, //!< Internal assertion failed (maps from STK_ASSERT). + KERNEL_PANIC_HRT_HARD_FAULT = 4U, //!< Kernel running in KERNEL_HRT mode reported deadline failure of the task. + KERNEL_PANIC_CPU_EXCEPTION = 5U, //!< CPU reported an exception and halted execution. + KERNEL_PANIC_CS_NESTING_OVERFLOW = 6U, //!< Critical section nesting limit exceeded: violation of STK_CRITICAL_SECTION_NESTINGS_MAX. + KERNEL_PANIC_UNKNOWN_SVC = 7U, //!< Unknown service command received by SVC handler. + KERNEL_PANIC_BAD_STATE = 8U, //!< Kernel entered unexpected (bad) state. + KERNEL_PANIC_BAD_MODE = 9U, //!< Kernel is in bad/unsupported mode for the current operation. + KERNEL_PANIC_BAD_STACK_TYPE = 10U //!< Stack type is unknown. }; /*! \enum EStackType @@ -208,8 +209,49 @@ static constexpr Weight DEFAULT_WEIGHT = static_cast(1); \note ISR-safe (bitmask arithmetic only, no kernel calls). \see TID_ISR_N */ -static inline bool IsIsrTid(TId tid) { return ((tid & TID_ISR_N) == TID_ISR_N); } +static inline bool IsIsrTid(TId id) { return ((id & TID_ISR_N) == TID_ISR_N); } +/*! \class ArrayView + \brief Lightweight, non-owning view over a contiguous sequence of elements. + \note This descriptor provides a safe encapsulation over raw pointers without + copying the underlying data. + + Usage example: + \code + stk::ITask *tasks[TASKS_MAX]; + ArrayView view(tasks, TASKS_MAX); + \endcode +*/ +template class ArrayView +{ +public: +/*! \brief Construct an ArrayView from a raw pointer and size. + \param[in] ptr: Pointer to the first element of the contiguous memory block. + \param[in] size: Number of elements available in the memory block. + */ + ArrayView(T *ptr, size_t size) : m_ptr(ptr), m_size(size) + {} + + /*! \brief Subscript operator for element access. + \param index Element index to access. + \return Reference to the element at the specified index. + \warning Triggers STK_ASSERT if index is out of bounds in a Debug build. + */ + T &operator[](size_t index) const + { + STK_ASSERT(index < m_size); + return m_ptr[index]; + } + + /*! \brief Get number of elements in the view. + \return The size of the array view. + */ + size_t GetSize() const { return m_size; } + +private: + T *m_ptr; //!< Pointer to the underlying memory block. + size_t m_size; //!< Total number of elements in the view. +}; /*! \class StackMemoryDef \brief Stack memory type definition. @@ -236,14 +278,14 @@ template struct StackMemoryDef */ struct Stack { - Word SP; //!< Stack Pointer (SP) register (note: must be the first entry in this struct). - EAccessMode mode; //!< Hardware access mode of the owning task (see \a EAccessMode). + Word SP; //!< Stack Pointer (SP) register (note: must be the first entry in this struct). + EAccessMode access_mode; //!< Hardware access mode of the owning task (see \a EAccessMode). #if STK_STACK_NEEDS_TASK_ID - TId tid; //!< Task id (see \a STK_SEGGER_SYSVIEW). + TId tid; //!< Task id (see \a STK_SEGGER_SYSVIEW). #endif #ifdef _STK_ARCH_ARM_CORTEX_M #if STK_TLS && !STK_TLS_PREFER_REGISTER - Word tls; //!< Thread-local storage if not using ARM Cortex-M R9 register for a fast inline access to TLS. + Word tls; //!< Thread-local storage if not using ARM Cortex-M R9 register for a fast inline access to TLS. #endif #endif }; @@ -275,12 +317,11 @@ class IStackMemory */ virtual size_t GetStackSpace() const { - const Word *stack = GetStack(); - const size_t stack_size = GetStackSize(); - + ArrayView stack(GetStack(), GetStackSize()); + // count leading Words equal to STK_STACK_MEMORY_FILLER (watermark) size_t space = 0U; - for ( ; (space < stack_size) && (stack[space] == STK_STACK_MEMORY_FILLER); ++space) + for ( ; (space < stack.GetSize()) && (stack[space] == STK_STACK_MEMORY_FILLER); ++space) {} return space; @@ -327,6 +368,11 @@ class IWaitObject : public util::DListEntry \return Returns \a true if update caused a timeout of the object, \a false otherwise. */ virtual bool Tick(Timeout elapsed_ticks) = 0; + +protected: + /*! \brief Destructor. + */ + ~IWaitObject() = default; }; /*! \class ITraceable @@ -367,6 +413,10 @@ class ITraceable } protected: + /*! \brief Destructor. + */ + ~ITraceable() = default; + #if STK_SYNC_DEBUG_NAMES const char *m_trace_name; //!< name (debug/tracing only) #endif @@ -380,11 +430,6 @@ class ISyncObject : public util::DListEntry friend class IKernel; public: - /*! \brief Destructor. - \note MISRA deviation: [STK-DEV-005] Rule 10-3-2. - */ - ~ISyncObject() = default; - /*! \typedef ListHeadType \brief List head type for ISyncObject elements. */ @@ -442,6 +487,11 @@ class ISyncObject : public util::DListEntry */ explicit ISyncObject() : m_wait_list() {} + + /*! \brief Destructor. + \note MISRA deviation: [STK-DEV-005] Rule 10-3-2. + */ + ~ISyncObject() = default; /*! \brief Wake the first task in the wait list (FIFO order). \note The woken task is notified with timeout=false, indicating a successful signal @@ -452,7 +502,9 @@ class ISyncObject : public util::DListEntry void WakeOne() { if (!m_wait_list.IsEmpty()) + { static_cast(m_wait_list.GetFirst())->Wake(false); + } } /*! \brief Wake all tasks currently in the wait list. @@ -464,7 +516,9 @@ class ISyncObject : public util::DListEntry void WakeAll() { while (!m_wait_list.IsEmpty()) + { static_cast(m_wait_list.GetFirst())->Wake(false); + } } IWaitObject::ListHeadType m_wait_list; //!< tasks blocked on this object @@ -501,6 +555,11 @@ class IMutex /*! \brief Unlock the mutex. */ virtual void Unlock() = 0; + +protected: + /*! \brief Destructor. + */ + ~IMutex() = default; }; /*! \class ITask @@ -614,10 +673,11 @@ class IKernelTask : public util::DListEntry /*! \brief Get user task. */ virtual ITask *GetUserTask() = 0; - - /*! \brief Get pointer to the user task's stack. + + /*! \brief Get user task's Stack info. + \param[out] stack: Stack info. */ - virtual Stack *GetUserStack() = 0; + virtual Stack GetUserStack() const = 0; /*! \brief Get static base weight assigned to the task. \return Static weight value of the task. @@ -670,6 +730,11 @@ class IKernelTask : public util::DListEntry will trigger an assertion in the concrete implementation. */ virtual void Wake() = 0; + +protected: + /*! \brief Destructor. + */ + ~IKernelTask() = default; }; /*! \class IPlatform @@ -918,6 +983,11 @@ class IPlatform \note ISR-safe. */ virtual void Resume(Timeout elapsed_ticks) = 0; + +protected: + /*! \brief Destructor. + */ + ~IPlatform() = default; }; /*! \class ITaskSwitchStrategy @@ -1031,6 +1101,11 @@ class ITaskSwitchStrategy STK_UNUSED(task); STK_UNUSED(old_weight); } + +protected: + /*! \brief Destructor. + */ + ~ITaskSwitchStrategy() = default; }; /*! \class IKernel @@ -1044,12 +1119,12 @@ class IKernel /*! \enum EState \brief Kernel state. */ - enum EState : uint8_t + enum EKernelState : uint8_t { - STATE_INACTIVE = 0, //!< Not ready, IKernel::Initialize() must be called. - STATE_READY, //!< Ready to start, IKernel::Start() must be called. - STATE_RUNNING, //!< Initialized and running, IKernel::Start() was called successfully. - STATE_SUSPENDED //!< Scheduling is suspended with IKernelService::Suspend(). + KSTATE_INACTIVE = 0, //!< Not ready, IKernel::Initialize() must be called. + KSTATE_READY, //!< Ready to start, IKernel::Start() must be called. + KSTATE_RUNNING, //!< Initialized and running, IKernel::Start() was called successfully. + KSTATE_SUSPENDED //!< Scheduling is suspended with IKernelService::Suspend(). }; /*! \brief Initialize kernel. @@ -1114,20 +1189,18 @@ class IKernel virtual void ResumeTask(ITask *user_task) = 0; /*! \brief Enumerate kernel tasks. - \param[in,out] tasks: Pointer to the array for IKernelTask pointers. - \param[in] max_size: Max size of the provided array. + \param[in] tasks: Reference to the ArrayView of IKernelTask pointers. \return Number of tasks in the array. \warning ISR-safe. */ - virtual size_t EnumerateKernelTasks(IKernelTask **tasks, size_t max_size) = 0; + virtual size_t EnumerateKernelTasks(ArrayView tasks) = 0; /*! \brief Enumerate user tasks. - \param[in,out] user_tasks: Pointer to the array for ITask pointers. - \param[in] max_size: Max size of the provided array. + \param[in] user_tasks: Reference to the ArrayView of ITask pointers. \return Number of tasks in the array. \warning ISR-safe. */ - virtual size_t EnumerateTasks(ITask **user_tasks, size_t max_size) = 0; + virtual size_t EnumerateTasks(ArrayView user_tasks) = 0; /*! \brief Enumerate tasks, invoking a callback for each active task. \tparam TMaxCount: Maximum number of tasks to enumerate. @@ -1152,15 +1225,17 @@ class IKernel template size_t EnumerateTasksT(TCallback &&callback) { - STK_STATIC_ASSERT(TMaxCount > 0); + STK_STATIC_ASSERT(TMaxCount > 0U); - ITask *tasks[TMaxCount] = {}; - size_t i = 0, count = EnumerateTasks(tasks, TMaxCount); + ITask *tasks[TMaxCount] = {}; + size_t count = EnumerateTasks(ArrayView(tasks, TMaxCount)); + size_t i = 0U; + bool fetch_next = true; - while (i < count) + while ((i < count) && fetch_next) { - if (!callback(tasks[i++])) - break; + fetch_next = callback(tasks[i]); + ++i; } return i; @@ -1176,7 +1251,7 @@ class IKernel \return Kernel state. \see EState */ - virtual EState GetState() const = 0; + virtual EKernelState GetState() const = 0; /*! \brief Get platform driver instance. \return Pointer to the IPlatform concrete class instance. @@ -1187,6 +1262,11 @@ class IKernel \return Pointer to the ITaskSwitchStrategy concrete class instance. */ virtual ITaskSwitchStrategy *GetSwitchStrategy() = 0; + +protected: + /*! \brief Destructor. + */ + ~IKernel() = default; }; /*! \class IKernelService @@ -1327,6 +1407,11 @@ class IKernelService \note ISR-safe. */ virtual void RestoreWeight(TId tid, ISyncObject *sobj = nullptr) = 0; + +protected: + /*! \brief Destructor. + */ + ~IKernelService() = default; }; } // namespace stk diff --git a/stk/include/stk_defs.h b/stk/include/stk_defs.h index 0ce6734..ab98e1d 100644 --- a/stk/include/stk_defs.h +++ b/stk/include/stk_defs.h @@ -39,7 +39,15 @@ \see KERNEL_TICKLESS, STK_TICKLESS_USE_ARM_DWT, STK_TICKLESS_TICKS_MAX */ #ifndef STK_TICKLESS_IDLE - #define STK_TICKLESS_IDLE 0 + #define STK_TICKLESS_IDLE (0) +#endif + +/*! \def STK_STRICT_COMPLIANCY + \brief Allow the use of workarounds to make binary smaller and faster. + \note Applied workarounds will break safety-critical rules (MISRA, etc). +*/ +#ifndef STK_STRICT_COMPLIANCY + #define STK_STRICT_COMPLIANCY (0) #endif /*! \def STK_TICKLESS_USE_ARM_DWT @@ -51,7 +59,7 @@ timer rearm and does not require rearm-error compensation. */ #ifndef STK_TICKLESS_USE_ARM_DWT - #define STK_TICKLESS_USE_ARM_DWT 1 + #define STK_TICKLESS_USE_ARM_DWT (1) #endif /*! \def STK_TICKLESS_TICKS_MAX @@ -65,7 +73,7 @@ \see STK_TICKLESS_IDLE, KERNEL_TICKLESS */ #ifndef STK_TICKLESS_TICKS_MAX - #define STK_TICKLESS_TICKS_MAX 1000 + #define STK_TICKLESS_TICKS_MAX (1000) #endif #if STK_TICKLESS_TICKS_MAX > 100000 #error "STK_TICKLESS_TICKS_MAX is too large: cpu_ticks_requested may overflow uint32_t." @@ -87,7 +95,7 @@ \see STK_TLS_PREFER_REGISTER, stk::hw::GetTlsPtr, stk::hw::SetTlsPtr */ #ifndef STK_TLS - #define STK_TLS 0 + #define STK_TLS (0) #endif /*! \def STK_TLS_PREFER_REGISTER @@ -115,7 +123,7 @@ \see STK_TLS, stk::hw::GetTlsPtr, stk::hw::SetTlsPtr */ #ifndef STK_TLS_PREFER_REGISTER - #define STK_TLS_PREFER_REGISTER 0 + #define STK_TLS_PREFER_REGISTER (0) #endif /*! \def STK_NEED_TASK_ID @@ -127,7 +135,7 @@ \see STK_SEGGER_SYSVIEW, stk::Stack */ #if STK_SEGGER_SYSVIEW - #define STK_STACK_NEEDS_TASK_ID 1 + #define STK_STACK_NEEDS_TASK_ID (1) #endif /*! \def STK_SYNC_DEBUG_NAMES @@ -138,9 +146,18 @@ \note Default: 0 (disabled) unless STK_SEGGER_SYSVIEW is active. */ #if !defined(STK_SYNC_DEBUG_NAMES) && STK_SEGGER_SYSVIEW - #define STK_SYNC_DEBUG_NAMES 1 + #define STK_SYNC_DEBUG_NAMES (1) #elif !defined(STK_SYNC_DEBUG_NAMES) - #define STK_SYNC_DEBUG_NAMES 0 + #define STK_SYNC_DEBUG_NAMES (0) +#endif + +/*! \def STK_VIRT_DTOR + \brief Makes destructors virtual and compliant to strict rules if STK_STRICT_COMPLIANCY=0. +*/ +#if !STK_STRICT_COMPLIANCY + #define STK_VIRT_DTOR +#else + #define STK_VIRT_DTOR virtual #endif /*! \def __stk_forceinline @@ -357,6 +374,16 @@ #define __stk_debug_break() #endif +/*! \def __stk_constexpr_cpp17 + \brief constexpr definition for C++17 and above. + \note Can be used as 'if __stk_constexpr_cpp17 (x) {}' with C++11 without a warning. +*/ +#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L)) + #define __stk_constexpr_cpp17 constexpr +#else + #define __stk_constexpr_cpp17 +#endif + /*! \def STK_ASSERT \brief Runtime assertion. Halts execution if the expression \a e evaluates to false. \note Behaviour depends on build configuration: @@ -428,7 +455,7 @@ Can be overridden by defining STK_STACK_MEMORY_FILLER before including this header or in stk_config.h. */ #ifndef STK_STACK_MEMORY_FILLER - #define STK_STACK_MEMORY_FILLER ((stk::Word)((sizeof(stk::Word) <= 4U) ? 0xdeadbeefu : 0xdeadbeefdeadbeefull)) + #define STK_STACK_MEMORY_FILLER (static_cast((sizeof(stk::Word) <= 4U) ? 0xDEADBEEFU : 0xDEADBEEFDEADBEEFULL)) #endif /*! \def STK_STACK_MEMORY_ALIGN @@ -436,11 +463,11 @@ */ #ifndef STK_STACK_MEMORY_ALIGN #if defined(__riscv) - #define STK_STACK_MEMORY_ALIGN 16U + #define STK_STACK_MEMORY_ALIGN (16U) #elif defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64) - #define STK_STACK_MEMORY_ALIGN 8U + #define STK_STACK_MEMORY_ALIGN (8U) #else // ARM, others - #define STK_STACK_MEMORY_ALIGN 4U + #define STK_STACK_MEMORY_ALIGN (4U) #endif #endif @@ -455,7 +482,7 @@ Can be overridden in stk_config.h based on Worst-Case Stack Usage (WCSU) analysis. */ #ifndef STK_CRITICAL_SECTION_NESTINGS_MAX - #define STK_CRITICAL_SECTION_NESTINGS_MAX 16U + #define STK_CRITICAL_SECTION_NESTINGS_MAX (16U) #endif /*! \def STK_ARCH_CPU_COUNT @@ -465,7 +492,7 @@ targets. Can be defined in the architecture header or stk_config.h. */ #ifndef STK_ARCH_CPU_COUNT - #define STK_ARCH_CPU_COUNT 1U + #define STK_ARCH_CPU_COUNT (1U) #endif /*! \def STK_STACK_SIZE_MIN @@ -488,7 +515,7 @@ #if defined(__riscv_32e) && (__riscv_32e == 1) // RISC-V RV32E (Embedded): Small 16-register file #if !defined(__riscv_flen) || (__riscv_flen == 0) - #define STK_STACK_SIZE_MIN 32U + #define STK_STACK_SIZE_MIN (32U) #else // FPU present: Requires additional space for 32 FP registers #define STK_STACK_SIZE_MIN (32U + (__riscv_flen * 2)) @@ -497,7 +524,7 @@ // Standard RISC-V (RV32I/RV64I): Large 32-register file // Higher minimum to prevent memory corruption on platforms like RP2350 #if !defined(__riscv_flen) || (__riscv_flen == 0) - #define STK_STACK_SIZE_MIN 256U + #define STK_STACK_SIZE_MIN (256U) #else // Standard RISC-V with FPU: Maximum frame allocation #define STK_STACK_SIZE_MIN (512U + (__riscv_flen * 2)) @@ -505,7 +532,7 @@ #endif #else // ARM Cortex-M and other architectures - #define STK_STACK_SIZE_MIN 32U + #define STK_STACK_SIZE_MIN (32U) #endif #endif @@ -580,13 +607,13 @@ struct STK_ALLOCATE_COUNT /*! \def STK_UNUSED \brief Explicitly marks a variable as unused to suppress compiler warnings. */ -#define STK_UNUSED(X) static_cast(X) +#define STK_UNUSED(X) static_cast((X)) /*! \brief A wrapper for a built-in memcpy, redefine to your own if required. \note Can be overridden by defining _STK_CUSTOM_MEMCPY in system configuration. */ #ifndef _STK_CUSTOM_MEMCPY -#include +#include static __stk_forceinline void STK_MEMCPY(void *const dest, const void *const src, const size_t size) { /* MISRA-compliant explicitly-typed call to underlying implementation */ diff --git a/stk/include/stk_helper.h b/stk/include/stk_helper.h index df217f5..d79fb05 100644 --- a/stk/include/stk_helper.h +++ b/stk/include/stk_helper.h @@ -172,6 +172,11 @@ class StackMemoryWrapper : public IStackMemory MemoryType *m_stack; //!< Pointer to the externally-owned stack memory array. }; +// Helper function for Kernel::UpdateTaskState. +template inline Timeout GetInitialSleepTicks(); +template <> inline Timeout GetInitialSleepTicks() { return STK_TICKLESS_TICKS_MAX; } +template <> inline Timeout GetInitialSleepTicks() { return 1; } + //! Implementation of ISyncObject::Tick, see \a ISyncObject. Placed here as it depends on hw namespace. inline bool ISyncObject::Tick(Timeout elapsed_ticks) { @@ -192,10 +197,12 @@ inline bool ISyncObject::Tick(Timeout elapsed_ticks) while (itr != nullptr) { - IWaitObject *next = static_cast(itr->GetNext()); + IWaitObject *const next = static_cast(itr->GetNext()); if (!itr->Tick(elapsed_ticks)) + { itr->Wake(true); + } itr = next; } @@ -207,13 +214,17 @@ inline bool ISyncObject::Tick(Timeout elapsed_ticks) inline Weight ISyncObject::FindWeightHigherThan(Weight comp) const { Weight max_weight = NO_WEIGHT; + const IWaitObject *itr = static_cast(m_wait_list.GetFirst()); - for (const IWaitObject *itr = static_cast(m_wait_list.GetFirst()); (itr != nullptr); - itr = static_cast(itr->GetNext())) + while (itr != nullptr) { - Weight w = GetUserTaskFromTid(itr->GetTid())->GetWeight(); + const Weight w = GetUserTaskFromTid(itr->GetTid())->GetWeight(); if (w > max_weight) + { max_weight = w; + } + + itr = static_cast(itr->GetNext()); } return ((max_weight > comp) ? max_weight : NO_WEIGHT); @@ -235,14 +246,14 @@ __stk_forceinline TId GetTid() } /*! \brief Convert ticks to milliseconds. - \param[in] ticks: Tick count to convert. + \param[in] tick_count: Tick count to convert. \param[in] resolution: Microseconds per tick, as returned by IKernelService::GetTickResolution(). \return Equivalent time in milliseconds. \note ISR-safe (performs only arithmetic, no kernel calls). */ -__stk_forceinline Time GetMsFromTicks(Ticks ticks, uint32_t resolution) +__stk_forceinline Time GetMsFromTicks(Ticks tick_count, uint32_t resolution) { - return static_cast