SuperTinyKernel™ RTOS 1.06.x
Lightweight, high-performance, deterministic, bare-metal C++ RTOS for resource-constrained embedded systems. MIT Open Source License.
Loading...
Searching...
No Matches
freertos_stk.cpp
Go to the documentation of this file.
1/*
2 * SuperTinyKernel(TM) RTOS: Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems.
3 *
4 * Source: https://github.com/SuperTinyKernel-RTOS
5 *
6 * Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com>. All Rights Reserved.
7 * License: MIT License, see LICENSE for a full text.
8 */
9
10#include <stdio.h> // snprintf (vTaskList)
11
12#include "stk.h"
13#include "sync/stk_sync.h"
14#include "time/stk_time.h"
15#include "memory/stk_memory.h"
16
17// See design notes, API coverage and other details in FreeRTOS.h.
18
19#include "FreeRTOS.h"
20
21// -----------------------------------------------------------------------------
22// Wrapper version info
23// -----------------------------------------------------------------------------
24
25#define FREERTOS_STK_WRAPPER_VERSION "FreeRTOS-STK Wrapper v1.0"
26
27// -----------------------------------------------------------------------------
28// Kernel configuration
29// -----------------------------------------------------------------------------
30
31// Maximum concurrent tasks. Mirrors FREERTOS_STK_MAX_TASKS from the header.
32#ifndef FREERTOS_STK_MAX_TASKS
33# define FREERTOS_STK_MAX_TASKS 16U
34#endif
35
36#ifndef FREERTOS_STK_DEFAULT_STACK_WORDS
37# define FREERTOS_STK_DEFAULT_STACK_WORDS 256U
38#endif
39
40// Minimum usable stack in STK Words.
41#define FREERTOS_STK_MIN_STACK_WORDS STK_STACK_SIZE_MIN
42
43// Returns a size of memory in stk::Word elements required for object allocation.
44template <typename T> static constexpr size_t StkGetWordCountForType()
45{
46 return ((sizeof(T) + sizeof(stk::Word) - 1) / sizeof(stk::Word));
47}
48
49// Custom strcmp replacement.
50static int32_t FreertosStrcmp(const char str1[], const char str2[]) // MISRA: declared as array, not pointer to allow indexed access
51{
52 size_t index = 0U;
53 int32_t result = 0;
54
55 // Loop until the end of either string or until a mismatch is found
56 while ((str1[index] != '\0') && (str2[index] != '\0') && (str1[index] == str2[index]))
57 {
58 index++;
59 }
60
61 // Calculate the difference between the characters where the loop stopped
62 // Cast to int to match standard strcmp return type
63 result = static_cast<int32_t>(str1[index]) - static_cast<int32_t>(str2[index]);
64
65 return result;
66}
67
68// -----------------------------------------------------------------------------
69// Private memory allocators (we define malloc, free here to overcome absence of declaration in
70// case of -ffreestanding compiler flag).
71// Similar to FreeRTOS's heap_3.c.
72// -----------------------------------------------------------------------------
73
74extern "C" void *malloc(size_t size);
75extern "C" void free(void *ptr);
76
78
80{
81 if (size == 0)
82 return nullptr;
83
84 // align to platform word
85 const size_t alignment = alignof(size_t);
86
87 // add header and round up to the nearest multiple of 'alignment'
88 size = (size + sizeof(size_t) + (alignment - 1)) & ~(alignment - 1);
89
91
92 if (s_MemStats.GetAvailable() < size)
93 return nullptr;
94
95 size_t *region = static_cast<size_t *>(malloc(size));
96 if (region != nullptr)
97 {
98 s_MemStats.RecordAllocate(size);
99
100 // save size
101 region[0] = size;
102
103 // set to usable memory region skipping header
104 region = region + 1;
105 }
106
107 return region;
108}
109
111{
112 if (ptr == nullptr)
113 return;
114
115 stk::hw::CriticalSection::ScopedLock cs_;
116
117 // get region
118 size_t *region = static_cast<size_t *>(ptr) - 1;
119 size_t size = region[0];
120
121 free(region);
122
124}
125
130
131// ===========================================================================
132// Internal helpers
133// ===========================================================================
134
135// Heap-allocate an object (operator new with nothrow).
136template <typename T, typename... Args>
137static T *ObjAlloc(Args &&...args)
138{
139 T *obj = nullptr;
140
141 void *ptr = pvPortMalloc(sizeof(T));
142 if (ptr != nullptr)
143 obj = new (ptr) T(static_cast<Args &&>(args)...);
144
145 return obj;
146}
147
148// Delete an object previously created by ObjAlloc.
149template <typename T>
150static void ObjFree(T *obj)
151{
152 if (obj != nullptr)
153 {
154 obj->~T();
155
156 if (obj->m_cb_owned)
157 vPortFree(obj);
158 }
159}
160
161// Heap-allocate a raw byte array of 'count' elements of type T via pvPortMalloc.
162// Equivalent to: new (std::nothrow) T[count]
163template <typename T>
164static T *ObjAllocArray(size_t count)
165{
166 if (count == 0U)
167 return nullptr;
168
169 void *ptr = pvPortMalloc(sizeof(T) * count);
170 return static_cast<T *>(ptr);
171}
172
173// Free a raw array previously allocated by ObjAllocArray (no destructor calls).
174// Equivalent to: delete[] ptr
175static inline void ObjFreeArray(void *ptr)
176{
177 vPortFree(ptr);
178}
179
180// Destroy and free a single heap-allocated object that was created by ObjAlloc
181// but is not tracked by m_cb_owned (e.g. self-deleting timers, error-path cleanup).
182// Equivalent to: delete obj
183template <typename T>
184static void ObjFreeRaw(T *obj)
185{
186 if (obj != nullptr)
187 {
188 obj->~T();
189 vPortFree(obj);
190 }
191}
192
193// -----------------------------------------------------------------------------
194// Heap API — pvPortMalloc / vPortFree
195//
196// Both functions delegate directly to stk::memory::MemoryAllocator::Allocate
197// and ::Free, which are the single allocation seam for the entire wrapper.
198// Redefining those two functions (e.g. to point at a static pool allocator)
199// automatically redirects both internal STK allocations and any application
200// code that calls pvPortMalloc / vPortFree.
201// -----------------------------------------------------------------------------
202
203__stk_weak void *pvPortMalloc(size_t xWantedSize)
204{
205 return stk::memory::MemoryAllocator::Allocate(xWantedSize);
206}
207
212
213// -----------------------------------------------------------------------------
214// Heap query API — xPortGetFreeHeapSize / xPortGetMinimumEverFreeHeapSize /
215// vPortGetHeapStats / MemoryAllocator::GetStats
216//
217// All three functions read directly from s_MemStats, which is the single
218// authoritative accounting structure maintained by Allocate() and Free().
219//
220// xPortGetFreeHeapSize
221// Returns GetAvailable() — the number of bytes not yet handed out.
222// This is a point-in-time snapshot and may lag by one allocation if called
223// concurrently, matching FreeRTOS heap_4/heap_5 behaviour.
224//
225// xPortGetMinimumEverFreeHeapSize
226// Returns the minimum value GetAvailable() has ever reached since
227// system start (i.e. the high-water mark of heap pressure). The watermark
228// is updated inside Allocate() immediately after the 'allocated' counter is
229// incremented, so it is always <= the current free value.
230//
231// vPortGetHeapStats
232// Fills a HeapStats_t snapshot consistent with the FreeRTOS heap_4/heap_5
233// contract. Fields that require a traversal of free-block lists (number of
234// free blocks, smallest/largest free block) are not available without a
235// block-list allocator; they are reported as 0 and 1 respectively to signal
236// "at least one contiguous region exists" without asserting false precision.
237//
238// MemoryAllocator::GetStats
239// Returns the raw Stats struct for STK-native callers.
240// -----------------------------------------------------------------------------
241
243{
244 return s_MemStats.GetAvailable();
245}
246
248{
249 return s_MemStats.min_ever_free;
250}
251
253{
254 if (pxHeapStats == nullptr)
255 return;
256
258 const size_t free_now = snap.GetAvailable();
259
260 (*pxHeapStats) = {};
261
262 // Fields derived directly from s_MemStats accounting.
263 pxHeapStats->xAvailableHeapSpaceInBytes = free_now;
266 pxHeapStats->xNumberOfSuccessfulFrees = snap.free_count;
267
268 // Fields that require free-block-list traversal are unavailable without a
269 // block-list allocator (we delegate to malloc). Report conservative values:
270 // - largest free block : current free bytes (treat heap as one region)
271 // - smallest free block : 0 (unknown subdivision)
272 // - number of free blocks: 1 (at least one region exists while free > 0)
273 pxHeapStats->xSizeOfLargestFreeBlockInBytes = free_now;
274 pxHeapStats->xSizeOfSmallestFreeBlockInBytes = (free_now > 0U) ? 1U : 0U;
275 pxHeapStats->xNumberOfFreeBlocks = (free_now > 0U) ? 1U : 0U;
276}
277
278// -----------------------------------------------------------------------------
279// Priority mapping:
280// FreeRTOS range : 0 (lowest/idle) .. configMAX_PRIORITIES-1 (highest)
281// STK FP32 level range : 0 .. 31
282//
283// SwitchStrategyFixedPriority interprets GetWeight() as the raw priority
284// level directly (not a proportional weight). Higher numeric value means
285// higher priority and strictly preempts all lower levels. The mapping is
286// therefore a direct clamp — no shift needed:
287//
288// stk_priority = clamp(freertos_priority, 0, 31)
289//
290// configMAX_PRIORITIES must be <= 32 (compile-time assertion below).
291// If it is less than 32, only levels 0..configMAX_PRIORITIES-1 are used
292// and the upper FP32 slots remain empty, which is perfectly fine.
293// -----------------------------------------------------------------------------
294
295// Enforce that configMAX_PRIORITIES fits within the 32-level FP32 strategy.
296static_assert(configMAX_PRIORITIES <= 32U,
297 "configMAX_PRIORITIES exceeds SwitchStrategyFP32's 32 priority levels. "
298 "Reduce configMAX_PRIORITIES or instantiate SwitchStrategyFixedPriority "
299 "with a larger MAX_PRIORITIES template parameter and update FrtosKernel.");
300
301static inline int32_t FrtosPrioToStkWeight(UBaseType_t p)
302{
303 // Clamp to [0 .. configMAX_PRIORITIES-1] then pass through directly:
304 // STK FP32 level == FreeRTOS priority (both 0 = lowest).
307
308 return static_cast<int32_t>(p);
309}
310
311static inline UBaseType_t StkWeightToFrtosPrio(int32_t w)
312{
313 if (w < 0)
314 return 0U;
315
316 UBaseType_t p = static_cast<UBaseType_t>(w);
317
320
321 return p;
322}
323
324// -----------------------------------------------------------------------------
325// Timeout conversion:
326// portMAX_DELAY (0xFFFFFFFF) -> stk::WAIT_INFINITE
327// 0 -> stk::NO_WAIT
328// N -> N (ticks pass through directly)
329// -----------------------------------------------------------------------------
331{
332 if (t == portMAX_DELAY)
333 return stk::WAIT_INFINITE;
334
335 if (t == 0U)
336 return stk::NO_WAIT;
337
338 return static_cast<stk::Timeout>(t);
339}
340
341// -----------------------------------------------------------------------------
342// ISR context check
343// -----------------------------------------------------------------------------
344static inline bool IsIrqContext()
345{
346 return stk::hw::IsInsideISR();
347}
348
349// -----------------------------------------------------------------------------
350// Global kernel type alias.
351// KERNEL_DYNAMIC : tasks can be created/deleted at runtime.
352// KERNEL_SYNC : enables all synchronisation primitives.
353// SwitchStrategyFP32: Fixed-priority preemptive, 32 levels (0=lowest, 31=highest),
354// with round-robin within each level. This exactly mirrors FreeRTOS scheduling
355// semantics: the highest-priority ready task always runs immediately.
356// -----------------------------------------------------------------------------
358#if STK_TICKLESS_IDLE
360#endif
364
366
367// -----------------------------------------------------------------------------
368// vPortEnterCritical / vPortExitCritical (back taskENTER/EXIT_CRITICAL macros)
369// -----------------------------------------------------------------------------
374
379
381{
382 stk::Yield();
383}
384
385// ===========================================================================
386// Task control block
387//
388// FrtosTask wraps a single FreeRTOS task. It implements stk::ITask so the
389// kernel can schedule it, and IStackMemory so its own stack can be registered.
390//
391// Task notifications (index 0) are modelled as a counting semaphore whose
392// count represents the notification value. xTaskNotify / xTaskNotifyWait
393// provide the richer set-bits / overwrite semantics on top of a raw uint32_t
394// notification word protected by a critical section.
395// ===========================================================================
396
397struct FrtosTask : public stk::ITask
398{
399 enum class State : uint8_t
400 {
401 Ready, // in the ready/running queue
402 Suspended, // explicitly suspended via vTaskSuspend()
403 Deleted, // marked for removal, kernel slot being freed
404 };
405
406 explicit FrtosTask()
407 : m_func(nullptr), m_argument(nullptr), m_name(nullptr),
409 m_stack(nullptr), m_stack_size(0U),
410 m_stack_owned(false), m_cb_owned(true),
411 m_state(State::Ready),
413 {
414 for (size_t i = 0U; i < configNUM_THREAD_LOCAL_STORAGE_POINTERS; ++i)
415 m_tls[i] = nullptr;
416 }
417
418 virtual ~FrtosTask()
419 {
420 if (m_stack_owned && (m_stack != nullptr))
421 {
423 m_stack = nullptr;
424 }
425 }
426
427 // ---- stk::ITask ----
428 void Run() override
429 {
431 // KERNEL_DYNAMIC: returning removes the task automatically.
432 }
433
434 void OnExit() override
435 {
437 }
438
439 const stk::Word *GetStack() const override { return m_stack; }
440 size_t GetStackSize() const override { return m_stack_size; }
442 int32_t GetWeight() const override { return m_weight; }
443 const char *GetTraceName() const override { return m_name; }
444
445 void OnDeadlineMissed(uint32_t) override {}
446
447 // Stack high-water mark inspection:
448 // Returns the number of untouched Words at the stack base (filled with
449 // STK_STACK_MEMORY_FILLER during init).
450 size_t GetStackHighWaterMark() const { return GetStackSpace(); }
451
452 // ---- Members ----
455 const char *m_name;
456 volatile int32_t m_weight; // STK SWRR weight (priority+1)
458 size_t m_stack_size; // Words
460 bool m_cb_owned; // true -> heap-alloc, delete on removal
461 volatile State m_state;
462 uint32_t m_task_number; // monotonic serial, assigned at construction
463
464 // Monotonic counter incremented once per FrtosTask construction.
465 // Stored as a file-scope static so all tasks share a single sequence.
466 static uint32_t s_task_counter;
467
468 // Thread-local storage slots (configNUM_THREAD_LOCAL_STORAGE_POINTERS entries).
469 // Initialised to nullptr at construction; accessed via
470 // vTaskSetThreadLocalStoragePointer / pvTaskGetThreadLocalStoragePointer.
472
473#if configUSE_TASK_NOTIFICATIONS
474 // ---- Per-slot task notification state ----
475 // FreeRTOS supports configTASK_NOTIFICATION_ARRAY_ENTRIES independent
476 // notification slots per task. Each slot is fully independent: it has its
477 // own 32-bit value word, a pending flag (for eSetValueWithoutOverwrite), and
478 // a binary stk::sync::Semaphore that serves as the blocking primitive.
479 //
480 // The non-indexed API (xTaskNotifyGive / xTaskNotify / etc.) is implemented
481 // as thin wrappers that always address slot 0.
483 {
484 volatile uint32_t value;
485 volatile bool pending;
487
488 explicit NotifySlot() : value(0U), pending(false), sem(0U, 1U) {}
489
490 private:
492 };
493
495#endif // configUSE_TASK_NOTIFICATIONS
496};
497
498// Monotonic task serial counter; incremented once per FrtosTask construction.
499uint32_t FrtosTask::s_task_counter = 0U;
500
501// ===========================================================================
502// Queue control block
503//
504// Backed by stk::sync::MessageQueue.
505// ===========================================================================
506
507// Forward declaration: FrtosQueueSet is defined after FrtosQueue and FrtosSemaphore
508// so that both control blocks can hold a non-owning back-pointer to their registered set.
509struct FrtosQueueSet;
510
512{
513 // External storage constructor (caller supplies the data buffer).
514 explicit FrtosQueue(uint32_t cap, uint32_t msg_size, const char *name,
515 uint8_t *ext_buf)
516 : m_mq(ext_buf, static_cast<size_t>(cap), static_cast<size_t>(msg_size)),
517 m_buf_owned(false), m_cb_owned(true)
519 , m_set(nullptr)
520 #endif
521 {
522 m_mq.SetTraceName(name);
523 }
524
525 // Heap-allocated data buffer constructor.
526 explicit FrtosQueue(uint32_t cap, uint32_t msg_size, const char *name)
527 : m_mq(AllocBuffer(cap, msg_size),
528 static_cast<size_t>(cap),
529 static_cast<size_t>(msg_size)),
530 m_buf_owned(m_mq.IsStorageValid()), m_cb_owned(true)
532 , m_set(nullptr)
533 #endif
534 {
535 m_mq.SetTraceName(name);
536 }
537
539 {
540 if (m_buf_owned)
541 ObjFreeArray(m_mq.GetBuffer());
542 }
543
544 static uint8_t *AllocBuffer(uint32_t cap, uint32_t msg_size)
545 {
546 return ObjAllocArray<uint8_t>(static_cast<size_t>(cap) * msg_size);
547 }
548
549 // ---- Members ----
553#if configUSE_QUEUE_SETS
555#endif
556};
557
558// ===========================================================================
559// Semaphore / Mutex control block
560//
561// A single struct covers:
562// - Binary semaphore (max_count=1, initial_count=0) SemKind::Counting (0x80)
563// - Counting semaphore (max_count=N, initial_count=K) SemKind::Counting (0x80)
564// - Mutex / Recursive (uses stk::sync::Mutex) SemKind::Mutex (0x81)
565//
566// The first byte of every FrtosSemaphore is its SemKind discriminant.
567// Call GetSemKindFromHandle() to safely classify an opaque handle.
568// ===========================================================================
569
570// SemKind — type discriminant stored as the first byte of every FrtosSemaphore.
571//
572// Values are chosen so they can never collide with byte[0] of a FrtosQueue or
573// any other FrtosXxx object:
574//
575// FrtosQueue byte[0] is byte[0] of stk::sync::MessageQueue, which inherits
576// from ITraceable. ITraceable starts with a vtable pointer (or equivalent
577// first data member); on all supported 32/64-bit targets that pointer value
578// is always in high memory (>= 4), so byte[0] is always >= 4 under normal
579// linking. Choosing 0x80 and 0x81 for the SemKind values places them well
580// above the 0x00–0x03 range previously used and well below any realistic
581// vtable-pointer low byte on little-endian targets, making the discriminant
582// robust even if future struct layout changes shift ITraceable internals.
583//
584// GetSemKindFromHandle() is the single authoritative function that reads and
585// validates the discriminant; all type-switching code must call it instead of
586// reading byte[0] directly.
587enum class SemKind : uint8_t
588{
589 None = 0x00U,
590 Counting = 0x80U,
591 Mutex = 0x81U,
592};
593
594// Return the SemKind discriminant for the object at \a obj, or SemKind::None
595// if the byte does not match any known SemKind value (i.e. the handle points
596// to a FrtosQueue or another non-semaphore object).
597//
598// The switch enumerates only the valid SemKind enumerators; any other byte
599// value falls through to the default and returns SemKind::None, making the
600// check both exhaustive and forward-safe.
601static SemKind GetSemKindFromHandle(const void *obj)
602{
603 const uint8_t first_byte = *static_cast<const uint8_t *>(obj);
604
605 switch (first_byte)
606 {
607 case static_cast<uint8_t>(SemKind::Counting): return SemKind::Counting;
608 case static_cast<uint8_t>(SemKind::Mutex): return SemKind::Mutex;
609 default: return SemKind::None;
610 }
611}
612
614{
615 explicit FrtosSemaphore(SemKind kind, uint16_t initial, uint16_t max_count)
616 : m_kind(kind), m_cb_owned(true),
617 m_sem(nullptr)
619 , m_mtx(nullptr)
620#endif
622 , m_set(nullptr)
623 #endif
624 {
625 if (kind == SemKind::Counting)
626 m_sem = ObjAlloc<stk::sync::Semaphore>(initial, max_count);
627#if configUSE_MUTEXES
628 else
630#endif
631 }
632
634 {
636#if configUSE_MUTEXES
638#endif
639 }
640
641 // ---- Members ----
644 stk::sync::Semaphore *m_sem; // non-null for Counting kind
645#if configUSE_MUTEXES
646 stk::sync::Mutex *m_mtx; // non-null for Mutex kind
647#endif
648#if configUSE_QUEUE_SETS
650#endif
651};
652
653// ===========================================================================
654// Queue Set control block
655//
656// A queue set is a supervising FIFO whose payload elements are void* handles
657// (sizeof(void*) bytes each). When a member queue or semaphore transitions
658// from empty to non-empty, the member's own handle is written into this FIFO
659// via QueueSetNotify(). xQueueSelectFromSet() then does a blocking Get()
660// of one handle from the FIFO, returning it to the caller.
661//
662// The internal MessageQueue stores pointer-sized tokens and is given a
663// capacity equal to uxEventQueueLength supplied by the application (which
664// per the FreeRTOS API contract must be >= the sum of all member capacities).
665//
666// Thread safety:
667// QueueSetNotify() is called from both task and ISR context immediately
668// after a successful Put/Signal on a member, before the critical section
669// opened by that Put/Signal is released. TryPut() (NO_WAIT) on the set
670// queue is ISR-safe per the STK MessageQueue contract, so no additional
671// locking is required here.
672//
673// Ownership model:
674// FrtosQueueSet is always heap-allocated (m_cb_owned = true).
675// Member pointers stored in m_mq are non-owning; the application owns all
676// member objects independently and must call xQueueRemoveFromSet() before
677// deleting any member or the set.
678// ===========================================================================
679
681{
682 // The payload of every slot in m_token_mq is exactly one void* — the
683 // handle of the member that fired. We use a raw byte array as the
684 // MessageQueue backing store to avoid a separate heap allocation.
685 explicit FrtosQueueSet(UBaseType_t uxEventQueueLength)
686 : m_buf(nullptr), m_cb_owned(true),
687 m_token_mq(nullptr)
688 {
689 // Allocate the flat byte ring-buffer: N slots × sizeof(void*) bytes.
690 const size_t buf_bytes =
691 static_cast<size_t>(uxEventQueueLength) * sizeof(void *);
692
693 m_buf = ObjAllocArray<uint8_t>(buf_bytes);
694 if (m_buf == nullptr)
695 return;
696
698 m_buf,
699 static_cast<size_t>(uxEventQueueLength),
700 sizeof(void *));
701 }
702
708
709 bool IsValid() const { return (m_token_mq != nullptr); }
710
711 // ---- Members ----
712 uint8_t *m_buf;
715};
716
717// -----------------------------------------------------------------------------
718// QueueSetNotify — called after every successful send/signal on a member.
719//
720// Posts the member's handle (a void*) into the set's token queue using a
721// non-blocking TryPut(). If the set queue is full the notification is
722// silently dropped, matching the FreeRTOS behaviour (which documents that
723// xEventQueueLength must be large enough to hold every possible concurrent
724// notification from all members without overflow).
725//
726// This function is ISR-safe: TryPut() uses NO_WAIT and a ScopedCriticalSection
727// internally, both of which are safe from interrupt context.
728//
729// Parameters:
730// member_handle — the void* handle of the queue or semaphore that fired.
731// set — the FrtosQueueSet that member belongs to.
732// -----------------------------------------------------------------------------
733template <typename THost>
734static inline void QueueSetNotify(void *member_handle, THost *host)
735{
736#if configUSE_QUEUE_SETS
737 // m_set is non-null only when the member was registered with xQueueAddToSet.
738 // Post the member handle as a pointer-sized token. TryPut is ISR-safe.
739 if (host->m_set != nullptr)
740 host->m_set->m_token_mq->TryPut(&member_handle);
741#else
742 STK_UNUSED(member_handle);
743 STK_UNUSED(host);
744#endif
745}
746
747// ===========================================================================
748// Software timer control block [configUSE_TIMERS]
749//
750// Backed by stk::time::TimerHost.
751// A single global TimerHost is created lazily on the first xTimerCreate().
752// ===========================================================================
753
754#if configUSE_TIMERS
755
757
758// Static storage for the TimerHost (avoids heap for the host object itself).
760
762{
763 explicit FrtosTimer(const char *name,
764 TickType_t period,
765 bool auto_reload,
766 void *timer_id,
768 : m_name(name), m_period(period), m_auto_reload(auto_reload),
769 m_timer_id(timer_id), m_callback(cb), m_cb_owned(true)
770 {}
771
772 virtual ~FrtosTimer() {}
773
774 void OnExpired(stk::time::TimerHost * /*host*/) override
775 {
776 m_callback(static_cast<TimerHandle_t>(this));
777 }
778
779 static bool EnsureTimerHost()
780 {
781 if (g_TimerHost == nullptr)
782 {
785 }
786 return (g_TimerHost != nullptr);
787 }
788
789 const char *m_name;
790 TickType_t m_period; // ticks, stored for Reset/ChangePeriod
795};
796
797// ===========================================================================
798// PendCall / g_PendCallPipe / FrtosPendDrainer
799//
800// Design
801// ------
802// A deferred call is represented as a plain value struct (PendCall) holding
803// the callback pointer and its two parameters. All in-flight calls live in a
804// statically-allocated ring-buffer:
805//
806// static stk::sync::PipeT<PendCall, FREERTOS_STK_PEND_CALL_QUEUE_SIZE>
807// g_PendCallPipe;
808//
809// FrtosPendDrainer is a singleton TimerHost::Timer that is started once (as a
810// 1-tick auto-reload timer) the first time xTimerPendFunctionCall[FromISR]()
811// is called. On every OnExpired() tick it drains g_PendCallPipe to completion,
812// invoking each PendCall callback in the TimerHost handler task context —
813// exactly where FreeRTOS's timer daemon would run them.
814//
815// Advantages
816// --------------------------------
817// * Zero heap allocation per call — no pvPortMalloc / ObjFreeRaw per pend.
818// * ISR path no longer requires the allocator to be ISR-reentrant.
819// * Static RAM cost is fixed and visible at link time
820// (FREERTOS_STK_PEND_CALL_QUEUE_SIZE * sizeof(PendCall) bytes).
821// * No self-deleting object pattern; ownership is unconditionally clear.
822// * PipeT::TryWrite() (used from ISR) is ISR-safe via ScopedCriticalSection.
823// * PipeT::Write() (used from task context) supports a real blocking timeout.
824//
825// Lifecycle of g_PendDrainer
826// --------------------------
827// Constructed in static storage (g_PendDrainerBuf) on first use.
828// Started as a 1-tick auto-reload timer so OnExpired() is called every tick
829// while the pipe is non-empty. To avoid burning timer ticks when idle the
830// drainer stops itself when it finds the pipe empty, and is re-started by
831// xTimerPendFunctionCall[FromISR]() whenever a new call is enqueued.
832// ===========================================================================
833
834// Value type: one deferred call record (no virtual dispatch, no heap).
836{
838 void *param1;
839 uint32_t param2;
840};
841
842// Static ring-buffer — capacity set by FREERTOS_STK_PEND_CALL_QUEUE_SIZE.
844
845// Static storage for the singleton drainer timer (avoids a heap allocation).
848
849// Singleton drainer: drains g_PendCallPipe each time it fires, then stops
850// itself when the pipe is empty to avoid unnecessary timer ticks.
852{
853 void OnExpired(stk::time::TimerHost *host) override
854 {
855 PendCall call;
856 while (g_PendCallPipe.TryRead(call))
857 call.func(call.param1, call.param2);
858
859 // Pipe is empty: stop the auto-reload drainer until the next enqueue.
860 // host->Stop() is safe to call from OnExpired() because the TimerHost
861 // removes the timer from the active list before dispatching OnExpired.
862 if (host != nullptr)
863 host->Stop(*this);
864 }
865};
866
867// Ensure the drainer timer is constructed and started (idempotent; ISR-unsafe).
868// Must be called from task context only (same restriction as EnsureTimerHost).
869static bool EnsurePendDrainer()
870{
871 if (g_PendDrainer == nullptr)
873
874 // Re-start as a 1-tick auto-reload timer each time we have new work.
875 // Restart() is idempotent if already active.
876 return g_TimerHost->Restart(*g_PendDrainer, 1U, 1U);
877}
878
879// Kick the drainer from ISR context after a TryWrite succeeds.
880// Uses Start() with NO_WAIT which is ISR-safe via PipeT / ScopedCriticalSection.
881// If the drainer is already active this is a no-op (Restart would re-arm it,
882// which is fine; the extra tick is harmless).
884{
885 if ((g_PendDrainer != nullptr) && (g_TimerHost != nullptr))
886 g_TimerHost->Restart(*g_PendDrainer, 1U, 1U);
887}
888
889#endif // configUSE_TIMERS
890
891// ===========================================================================
892// Event group control block [configUSE_EVENT_GROUPS]
893//
894// Backed by stk::sync::EventFlags (32-bit; bits 0..30 are usable,
895// bit 31 is reserved by STK for error sentinels).
896// FreeRTOS conventionally uses only bits 0..23 for event groups.
897// ===========================================================================
898
899#if configUSE_EVENT_GROUPS
900
902{
903 explicit FrtosEventGroup() : m_ef(0U), m_cb_owned(true)
904 {}
905
908};
909
910#endif // configUSE_EVENT_GROUPS
911
912// -----------------------------------------------------------------------------
913// FrtosStreamBuffer [configUSE_STREAM_BUFFERS]
914//
915// Backed by a stk::sync::Pipe with element_size = 1 (byte ring-buffer).
916// sync::Pipe is chosen over sync::MessageQueue because it exposes WriteBulk /
917// ReadBulk / TryWriteBulk / TryReadBulk directly, which are required for
918// efficient multi-byte stream transfers and for the trigger-level logic in
919// xStreamBufferReceive().
920//
921// The data buffer is either heap-owned (m_buf_owned = true) or external
922// (caller-supplied via xStreamBufferCreateStatic).
923//
924// Trigger level: xStreamBufferReceive() delegates entirely to
925// Pipe::ReadBulkTriggered(), which blocks until m_trigger bytes are present
926// and then drains up to xBufferLengthBytes in one atomic CS pass —
927// no busy-spin, no second call, no lost-wakeup risk.
928// -----------------------------------------------------------------------------
929
930#if configUSE_STREAM_BUFFERS
931
933{
934 // Constructor: caller pre-allocates buf and may transfer ownership via
935 // m_buf_owned / m_cb_owned (overridden by the Create helpers after construction).
936 // pSendCb / pRecvCb are optional per-instance notification callbacks;
937 // both default to nullptr (no callback).
938 explicit FrtosStreamBuffer(uint8_t *buf,
939 size_t capacity,
940 size_t trigger,
941 StreamBufferCallbackFunction_t pSendCb = nullptr,
942 StreamBufferCallbackFunction_t pRecvCb = nullptr)
943 : m_pipe(buf, capacity, 1U),
944 m_trigger(trigger >= 1U ? trigger : 1U),
945 m_buf_owned(false), // overridden to true by xStreamBufferCreate after ctor
946 m_cb_owned(false), // overridden to true by xStreamBufferCreate after ctor
947 m_send_cb(pSendCb),
948 m_recv_cb(pRecvCb)
949 {}
950
952 {
953 if (m_buf_owned)
954 ObjFreeArray(m_pipe.GetBuffer());
955 }
956
957 // ---- Members ----
959 size_t m_trigger;
964};
965
966// -----------------------------------------------------------------------------
967// FrtosMessageBuffer
968//
969// An envelope struct is pushed into m_eq for every message. The payload lives
970// in a block pool block. On Receive() the envelope is popped, payload copied
971// out, and the block returned to the pool.
972//
973// Layout of the caller-supplied flat buffer for the static constructor:
974// [ block_pool_storage | envelope_queue_storage ]
975// where
976// block_pool_storage = xMessageCount * AlignBlockSize(xMaxMessageSize)
977// envelope_queue_storage = xMessageCount * sizeof(MsgEnvelope)
978// -----------------------------------------------------------------------------
980{
982 {
983 size_t len;
984 void *blk;
985 };
986
987 static constexpr size_t ENVELOPE_SIZE = sizeof(MsgEnvelope);
988
989 // Heap constructor.
990 // pSendCb / pRecvCb are optional per-instance notification callbacks;
991 // both default to nullptr (no callback).
992 explicit FrtosMessageBuffer(size_t max_msg_size,
993 size_t msg_count,
994 StreamBufferCallbackFunction_t pSendCb = nullptr,
995 StreamBufferCallbackFunction_t pRecvCb = nullptr)
996 : m_pool(msg_count,
997 stk::memory::BlockMemoryPool::AlignBlockSize(max_msg_size)),
998 m_eq(ObjAllocArray<uint8_t>(msg_count * ENVELOPE_SIZE),
999 msg_count, ENVELOPE_SIZE),
1000 m_max_msg_size(max_msg_size),
1001 m_eq_buf_owned(true),
1002 m_cb_owned(true),
1003 m_send_cb(pSendCb),
1004 m_recv_cb(pRecvCb)
1005 {}
1006
1007 // Static constructor: uses caller-supplied flat storage buffer.
1008 // Layout: [pool_storage | eq_storage] as described above.
1009 explicit FrtosMessageBuffer(size_t max_msg_size,
1010 size_t msg_count,
1011 uint8_t *storage,
1012 size_t storage_size,
1013 StreamBufferCallbackFunction_t pSendCb = nullptr,
1014 StreamBufferCallbackFunction_t pRecvCb = nullptr)
1015 : m_pool(msg_count,
1016 stk::memory::BlockMemoryPool::AlignBlockSize(max_msg_size),
1017 storage,
1018 msg_count * stk::memory::BlockMemoryPool::AlignBlockSize(max_msg_size)),
1019 m_eq(storage + msg_count * stk::memory::BlockMemoryPool::AlignBlockSize(max_msg_size),
1020 msg_count, ENVELOPE_SIZE),
1021 m_max_msg_size(max_msg_size),
1022 m_eq_buf_owned(false),
1023 m_cb_owned(false),
1024 m_send_cb(pSendCb),
1025 m_recv_cb(pRecvCb)
1026 {
1027 STK_UNUSED(storage_size);
1028 }
1029
1031 {
1032 if (m_eq_buf_owned)
1033 ObjFreeArray(static_cast<uint8_t *>(m_eq.GetBuffer()));
1034 }
1035
1036 // ---- Members ----
1044};
1045
1046#endif // configUSE_STREAM_BUFFERS
1047
1048// Ensure kernel is initialized.
1050{
1052 {
1053 g_StkKernel.Initialize(); // default 1 ms tick resolution
1054 }
1055}
1056
1057// ===========================================================================
1058// Kernel control
1059// ===========================================================================
1060
1062{
1064
1065 g_StkKernel.Start(); // does not return for KERNEL_DYNAMIC until all tasks exit
1066}
1067
1069{
1070 g_StkKernel.EnumerateTasksT<FREERTOS_STK_MAX_TASKS>([&](stk::ITask *task) -> bool
1071 {
1072 g_StkKernel.ScheduleTaskRemoval(task);
1073 return true;
1074 });
1075
1076 stk::Yield();
1077}
1078
1083
1085{
1087 return pdFALSE; // no pending switch tracked at wrapper level
1088}
1089
1091{
1092 return static_cast<TickType_t>(stk::GetTicks());
1093}
1094
1096{
1097 return static_cast<TickType_t>(stk::GetTicks()); // GetTicks() is ISR-safe
1098}
1099
1101{
1103 return static_cast<UBaseType_t>(g_StkKernel.GetSwitchStrategy()->GetSize());
1104}
1105
1107{
1108 // Map the four STK kernel states onto the three FreeRTOS scheduler states:
1109 // STATE_INACTIVE / STATE_READY -> NOT_STARTED (scheduler never ran)
1110 // STATE_RUNNING -> RUNNING
1111 // STATE_SUSPENDED -> SUSPENDED
1112 switch (g_StkKernel.GetState())
1113 {
1116 default: return taskSCHEDULER_NOT_STARTED;
1117 }
1118}
1119
1120// ===========================================================================
1121// Task management
1122// ===========================================================================
1123
1125 const char *pcName,
1126 uint32_t usStackDepth,
1127 void *pvParameters,
1128 UBaseType_t uxPriority,
1129 TaskHandle_t *pxCreatedTask)
1130{
1131 if (pvTaskCode == nullptr)
1132 return pdFAIL;
1133
1135 if (t == nullptr)
1136 return pdFAIL;
1137
1138 t->m_func = pvTaskCode;
1139 t->m_argument = pvParameters;
1140 t->m_name = pcName;
1141 t->m_weight = FrtosPrioToStkWeight(uxPriority);
1142
1143 // Determine stack size in Words.
1144 size_t stack_words = (usStackDepth > 0U)
1145 ? static_cast<size_t>(usStackDepth)
1147
1148 if (stack_words < FREERTOS_STK_MIN_STACK_WORDS)
1149 stack_words = FREERTOS_STK_MIN_STACK_WORDS;
1150
1151 t->m_stack = ObjAllocArray<stk::Word>(stack_words);
1152 if (t->m_stack == nullptr)
1153 {
1154 ObjFree(t);
1155 return pdFAIL;
1156 }
1157
1158 t->m_stack_size = stack_words;
1159 t->m_stack_owned = true;
1160
1162
1163 g_StkKernel.AddTask(t);
1164
1165 if (pxCreatedTask != nullptr)
1166 *pxCreatedTask = static_cast<TaskHandle_t>(t);
1167
1168 return pdPASS;
1169}
1170
1172 const char *pcName,
1173 uint32_t ulStackDepth,
1174 void *pvParameters,
1175 UBaseType_t uxPriority,
1176 StackType_t *puxStackBuffer,
1177 StaticTask_t *pxTaskBuffer)
1178{
1179 // All three pointer arguments are mandatory for static allocation.
1180 if ((pvTaskCode == nullptr) || (puxStackBuffer == nullptr) || (pxTaskBuffer == nullptr))
1181 return nullptr;
1182
1183 // Placement-new the FrtosTask control block into the caller-supplied TCB
1184 // buffer. Static assert guards against the buffer being too small.
1185 static_assert(sizeof(StaticTask_t) >= sizeof(FrtosTask),
1186 "StaticTask_t is too small to hold FrtosTask. "
1187 "Increase STATIC_TASK_TCB_SIZE_WORDS in freertos_stk.h.");
1188
1189 FrtosTask *t = new (pxTaskBuffer) FrtosTask();
1190
1191 t->m_func = pvTaskCode;
1192 t->m_argument = pvParameters;
1193 t->m_name = pcName;
1194 t->m_weight = FrtosPrioToStkWeight(uxPriority);
1195 t->m_stack = static_cast<stk::Word *>(static_cast<void *>(puxStackBuffer));
1196 t->m_stack_size = (ulStackDepth >= FREERTOS_STK_MIN_STACK_WORDS)
1197 ? static_cast<size_t>(ulStackDepth)
1199 t->m_stack_owned = false; // caller owns both the stack and the TCB
1200 t->m_cb_owned = false; // destructor must not delete — caller owns memory
1201
1203
1204 g_StkKernel.AddTask(t);
1205
1206 return static_cast<TaskHandle_t>(t);
1207}
1208
1209void vTaskDelete(TaskHandle_t xTaskToDelete)
1210{
1211 FrtosTask *t = (xTaskToDelete == nullptr) ? static_cast<FrtosTask *>(
1212 reinterpret_cast<FrtosTask *>(static_cast<uintptr_t>(stk::GetTid()))) :
1213 static_cast<FrtosTask *>(xTaskToDelete);
1214
1215 if (t == nullptr)
1216 return;
1217
1219
1220 g_StkKernel.ScheduleTaskRemoval(t);
1221
1222 // Detached tasks are freed when the slot is released.
1223 // For this wrapper, all tasks are considered detached (no join semantics).
1224 ObjFree(t);
1225}
1226
1227void vTaskSuspend(TaskHandle_t xTaskToSuspend)
1228{
1229 FrtosTask *t = (xTaskToSuspend == nullptr) ? static_cast<FrtosTask *>(
1230 reinterpret_cast<FrtosTask *>(static_cast<uintptr_t>(stk::GetTid()))) :
1231 static_cast<FrtosTask *>(xTaskToSuspend);
1232
1233 if (t == nullptr)
1234 return;
1235
1236 bool already = false;
1237 g_StkKernel.SuspendTask(t, already);
1239}
1240
1241void vTaskResume(TaskHandle_t xTaskToResume)
1242{
1243 if (xTaskToResume == nullptr)
1244 return;
1245
1246 FrtosTask *t = static_cast<FrtosTask *>(xTaskToResume);
1247
1249
1251 return;
1252
1253 g_StkKernel.ResumeTask(t);
1255}
1256
1258{
1259 if (xTaskToResume == nullptr)
1260 return pdFALSE;
1261
1262 FrtosTask *t = static_cast<FrtosTask *>(xTaskToResume);
1263
1265
1267 return pdFALSE;
1268
1269 g_StkKernel.ResumeTask(t);
1271
1272 return pdTRUE;
1273}
1274
1276{
1277 if (xTask == nullptr)
1278 return pdFALSE;
1279
1280 // Resolve NULL -> calling task (same convention used throughout the wrapper).
1281 const stk::TId tid = static_cast<stk::TId>(reinterpret_cast<uintptr_t>(xTask));
1282
1283 const FrtosTask *t = static_cast<const FrtosTask *>(xTask);
1285 return pdFAIL; // suspended or otherwise not in a delay-able state
1286
1287 stk::SleepCancel(tid);
1288 return pdPASS;
1289}
1290
1291void vTaskDelay(TickType_t xTicksToDelay)
1292{
1293 if (IsIrqContext())
1294 return;
1295
1296 stk::Sleep(FrtosTimeoutToStk(xTicksToDelay));
1297}
1298
1299void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement)
1300{
1301 static_cast<void>(xTaskDelayUntil(pxPreviousWakeTime, xTimeIncrement));
1302}
1303
1304BaseType_t xTaskDelayUntil(TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement)
1305{
1306 if (IsIrqContext() || (pxPreviousWakeTime == nullptr))
1307 return pdFALSE;
1308
1309 const stk::Ticks wake_at = static_cast<stk::Ticks>(*pxPreviousWakeTime) +
1310 static_cast<stk::Ticks>(xTimeIncrement);
1311
1312 *pxPreviousWakeTime = static_cast<TickType_t>(wake_at);
1313
1314 return stk::SleepUntil(wake_at) ? pdTRUE : pdFALSE;
1315}
1316
1318{
1319 FrtosTask *t = (xTask == nullptr) ? reinterpret_cast<FrtosTask *>(
1320 static_cast<uintptr_t>(stk::GetTid())) : static_cast<FrtosTask *>(xTask);
1321
1322 if (t == nullptr)
1323 return;
1324
1325 t->m_weight = FrtosPrioToStkWeight(uxNewPriority);
1326}
1327
1329{
1330 const FrtosTask *t = (xTask == nullptr) ? reinterpret_cast<const FrtosTask *>(
1331 static_cast<uintptr_t>(stk::GetTid())) : static_cast<const FrtosTask *>(xTask);
1332
1333 if (t == nullptr)
1334 return 0U;
1335
1336 return StkWeightToFrtosPrio(t->m_weight);
1337}
1338
1340{
1341 return uxTaskPriorityGet(xTask); // same implementation; GetTid() is ISR-safe
1342}
1343
1345{
1346 if (xTask == nullptr)
1347 return eInvalid;
1348
1349 FrtosTask *t = static_cast<FrtosTask *>(xTask);
1350
1352 return eDeleted;
1353
1355 return eSuspended;
1356
1357 // Check whether this is the currently running task.
1358 if (static_cast<uintptr_t>(stk::GetTid()) == reinterpret_cast<uintptr_t>(t))
1359 return eRunning;
1360
1361 return eReady;
1362}
1363
1365{
1366 if (IsIrqContext())
1367 return nullptr;
1368
1369 return reinterpret_cast<TaskHandle_t>(static_cast<uintptr_t>(stk::GetTid()));
1370}
1371
1372TaskHandle_t xTaskGetHandle(const char *pcNameToQuery)
1373{
1374 if (pcNameToQuery == nullptr)
1375 return nullptr;
1376
1377 // Enumerate all tasks and compare names.
1378 TaskHandle_t found = nullptr;
1379
1380 g_StkKernel.EnumerateTasksT<FREERTOS_STK_MAX_TASKS>([&](stk::ITask *task) -> bool
1381 {
1382 if ((task->GetTraceName() != nullptr) &&
1383 (FreertosStrcmp(task->GetTraceName(), pcNameToQuery) == 0))
1384 {
1385 found = static_cast<TaskHandle_t>(task);
1386 return false; // stop iteration
1387 }
1388 return true;
1389 });
1390
1391 return found;
1392}
1393
1394const char *pcTaskGetName(TaskHandle_t xTaskToQuery)
1395{
1396 if (xTaskToQuery == nullptr)
1397 return nullptr;
1398
1399 return static_cast<FrtosTask *>(xTaskToQuery)->m_name;
1400}
1401
1403{
1404 const FrtosTask *t = (xTask == nullptr) ? reinterpret_cast<const FrtosTask *>(
1405 static_cast<uintptr_t>(stk::GetTid())) : static_cast<const FrtosTask *>(xTask);
1406
1407 if (t == nullptr)
1408 return 0U;
1409
1410 return static_cast<UBaseType_t>(t->GetStackHighWaterMark());
1411}
1412
1414{
1415 const FrtosTask *t = (xTask == nullptr) ? reinterpret_cast<const FrtosTask *>(
1416 static_cast<uintptr_t>(stk::GetTid())) : static_cast<const FrtosTask *>(xTask);
1417
1418 if (t == nullptr)
1419 return 0U;
1420
1421 return static_cast<configSTACK_DEPTH_TYPE>(t->GetStackHighWaterMark());
1422}
1423
1425 UBaseType_t uxArraySize,
1426 uint32_t *pulTotalRunTime)
1427{
1428 // STK has no global CPU run-time accumulator; report 0 per the FreeRTOS
1429 // convention for targets that do not implement run-time statistics.
1430 if (pulTotalRunTime != nullptr)
1431 *pulTotalRunTime = 0U;
1432
1433 if ((pxTaskStatusArray == nullptr) || (uxArraySize == 0U))
1434 return 0U;
1435
1436 UBaseType_t filled = 0U;
1437
1438 // Identify the currently running task once, outside the enumeration
1439 // loop, so the eRunning check is consistent across all entries.
1440 const uintptr_t running_tid = static_cast<uintptr_t>(stk::GetTid());
1441
1442 g_StkKernel.EnumerateTasksT<FREERTOS_STK_MAX_TASKS>([&](stk::ITask *itask) -> bool
1443 {
1444 if (filled >= uxArraySize)
1445 return false; // array full — stop enumeration
1446
1447 const FrtosTask *t = static_cast<const FrtosTask *>(itask);
1448 TaskStatus_t &s = pxTaskStatusArray[filled];
1449
1450 // xHandle — the FrtosTask pointer cast to an opaque handle.
1451 s.xHandle = static_cast<TaskHandle_t>(const_cast<FrtosTask *>(t));
1452
1453 // pcTaskName — direct pointer into the task's name buffer (not a copy).
1454 s.pcTaskName = (t->m_name != nullptr) ? t->m_name : "";
1455
1456 // eCurrentState — mirrors eTaskGetState() logic.
1459 else
1462 else
1463 if (reinterpret_cast<uintptr_t>(t) == running_tid)
1465 else
1467
1468 // uxCurrentPriority / uxBasePriority — same value: STK has no
1469 // priority inheritance so the current and base priority are identical.
1472
1473 // ulRunTimeCounter — always 0 (STK has no per-task CPU accounting).
1474 s.ulRunTimeCounter = 0U;
1475
1476 // pxStackBase — bottom of the stack array (index 0).
1477 s.pxStackBase = reinterpret_cast<StackType_t *>(t->m_stack);
1478
1479 // usStackHighWaterMark — minimum observed free Words (watermark scan).
1482
1483 // xTaskNumber — monotonic serial assigned at construction.
1485
1486 ++filled;
1487 return true; // continue enumeration
1488 });
1489
1490 return filled;
1491}
1492
1494 TaskHandle_t *pxCreatedTask)
1495{
1496 // Mandatory pointer guard.
1497 if ((pxTaskDefinition == nullptr) ||
1498 (pxTaskDefinition->pvTaskCode == nullptr) ||
1499 (pxTaskDefinition->puxStackBuffer == nullptr) ||
1500 (pxTaskDefinition->pxTaskBuffer == nullptr))
1501 return pdFAIL;
1502
1503 // STK does not implement MPU support. Forward to xTaskCreateStatic(),
1504 // accepting but ignoring the xRegions MPU region table.
1505 // TODO: program pxTaskDefinition->xRegions into the MPU when STK gains
1506 // hardware MPU support.
1508 pxTaskDefinition->pvTaskCode,
1509 pxTaskDefinition->pcName,
1510 pxTaskDefinition->usStackDepth,
1511 pxTaskDefinition->pvParameters,
1512 pxTaskDefinition->uxPriority,
1513 pxTaskDefinition->puxStackBuffer,
1514 pxTaskDefinition->pxTaskBuffer);
1515
1516 if (h == nullptr)
1517 return pdFAIL;
1518
1519 if (pxCreatedTask != nullptr)
1520 *pxCreatedTask = h;
1521
1522 return pdPASS;
1523}
1524
1526 TaskHandle_t *pxCreatedTask)
1527{
1528 // Mandatory pointer guard (only pvTaskCode is required; the caller need not
1529 // supply puxStackBuffer or pxTaskBuffer — both are heap-allocated here).
1530 if ((pxTaskDefinition == nullptr) ||
1531 (pxTaskDefinition->pvTaskCode == nullptr))
1532 return pdFAIL;
1533
1534 // STK does not implement MPU support. Forward to xTaskCreate() which
1535 // heap-allocates both the TCB and the task stack, accepting but ignoring
1536 // the xRegions MPU region table.
1537 // TODO: program pxTaskDefinition->xRegions into the MPU when STK gains
1538 // hardware MPU support.
1539 return xTaskCreate(
1540 pxTaskDefinition->pvTaskCode,
1541 pxTaskDefinition->pcName,
1542 pxTaskDefinition->usStackDepth,
1543 pxTaskDefinition->pvParameters,
1544 pxTaskDefinition->uxPriority,
1545 pxCreatedTask);
1546}
1547
1548void vTaskList(char *pcWriteBuffer)
1549{
1550 if (pcWriteBuffer == nullptr)
1551 return;
1552
1553 // Write the column header that FreeRTOS vTaskList() produces, so that
1554 // existing log parsers find what they expect.
1555 int off = snprintf(pcWriteBuffer, 64U,
1556 "%-12s %c %4s %6s %4s\r\n",
1557 "Name", 'S', "Prio", "Stack", "Num");
1558
1559 if (off < 0) off = 0;
1560 char *p = pcWriteBuffer + off;
1561
1562 // Enumerate all tasks and fill one row per task.
1563 UBaseType_t task_num = 0U;
1564
1565 g_StkKernel.EnumerateTasksT<FREERTOS_STK_MAX_TASKS>([&](stk::ITask *itask) -> bool
1566 {
1567 ++task_num;
1568 FrtosTask *t = static_cast<FrtosTask *>(itask);
1569
1570 // Determine state letter, matching FreeRTOS convention:
1571 // X = Running, R = Ready, B = Blocked, S = Suspended, D = Deleted
1572 char state_letter;
1574 state_letter = 'D';
1575 else
1577 state_letter = 'S';
1578 else
1579 if (static_cast<uintptr_t>(stk::GetTid()) == reinterpret_cast<uintptr_t>(t))
1580 state_letter = 'X';
1581 else
1582 state_letter = 'R';
1583
1584 const char *name = (t->m_name != nullptr) ? t->m_name : "(unnamed)";
1586 size_t hwm = t->GetStackHighWaterMark();
1587
1588 int n = snprintf(p, 48U, "%-12s %c %4u %6u %4u\r\n",
1589 name, state_letter,
1590 static_cast<unsigned>(prio),
1591 static_cast<unsigned>(hwm),
1592 static_cast<unsigned>(task_num));
1593 if (n > 0) p += n;
1594
1595 return true; // continue enumeration
1596 });
1597
1598 *p = '\0'; // null-terminate
1599}
1600
1601// -----------------------------------------------------------------------------
1602// vTaskGetRunTimeStats
1603// -----------------------------------------------------------------------------
1604// Produces the standard FreeRTOS run-time statistics table:
1605//
1606// Task Abs Time % Time
1607// ----------------------------------------
1608// TaskName 0 0%
1609// ...
1610//
1611// STK has no per-task CPU run-time accumulator (ulRunTimeCounter is always 0
1612// and the total run time reported by uxTaskGetSystemState() is always 0).
1613// Following the FreeRTOS convention for targets where
1614// configGENERATE_RUN_TIME_STATS is disabled, both columns are emitted as 0.
1615// The function exists for link compatibility with middleware and diagnostic
1616// tools that call it unconditionally.
1617// -----------------------------------------------------------------------------
1618
1619void vTaskGetRunTimeStats(char *pcWriteBuffer)
1620{
1621 if (pcWriteBuffer == nullptr)
1622 return;
1623
1624 // Column header matching FreeRTOS vTaskGetRunTimeStats() output format so
1625 // that existing log parsers (SystemView, Tracealyzer, custom scripts) find
1626 // the layout they expect.
1627 int off = snprintf(pcWriteBuffer, 64U,
1628 "%-12s %12s %8s\r\n",
1629 "Task", "Abs Time", "% Time");
1630
1631 if (off < 0) off = 0;
1632 char *p = pcWriteBuffer + off;
1633
1634 g_StkKernel.EnumerateTasksT<FREERTOS_STK_MAX_TASKS>([&](stk::ITask *itask) -> bool
1635 {
1636 const FrtosTask *t = static_cast<const FrtosTask *>(itask);
1637 const char *name = (t->m_name != nullptr) ? t->m_name : "(unnamed)";
1638
1639 // ulRunTimeCounter is always 0: STK has no per-task CPU accounting.
1640 // Percentage is therefore also 0. Emit "<1%" only when a non-zero
1641 // total is available; here total is always 0 so we emit "0%".
1642 int n = snprintf(p, 48U, "%-12s %12lu %7lu%%\r\n",
1643 name,
1644 0UL, // ulRunTimeCounter
1645 0UL); // percentage
1646 if (n > 0) p += n;
1647
1648 return true; // continue enumeration
1649 });
1650
1651 *p = '\0'; // null-terminate
1652}
1653
1654// ===========================================================================
1655// Queue API
1656// ===========================================================================
1657
1659 UBaseType_t uxItemSize)
1660{
1661 if (IsIrqContext() || (uxQueueLength == 0U) || (uxItemSize == 0U))
1662 return nullptr;
1663
1664 if (uxQueueLength > stk::sync::MessageQueue::CAPACITY_MAX)
1665 return nullptr;
1666
1668 static_cast<uint32_t>(uxQueueLength),
1669 static_cast<uint32_t>(uxItemSize),
1670 nullptr /* name */);
1671
1672 if (q == nullptr)
1673 return nullptr;
1674
1675 if (!q->m_mq.IsStorageValid())
1676 {
1677 ObjFree(q);
1678 return nullptr;
1679 }
1680
1681 return static_cast<QueueHandle_t>(q);
1682}
1683
1685 UBaseType_t uxItemSize,
1686 uint8_t *pucQueueStorage,
1687 StaticQueue_t *pxStaticQueue)
1688{
1689 // All pointer arguments are mandatory for static allocation.
1690 if ((pucQueueStorage == nullptr) || (pxStaticQueue == nullptr))
1691 return nullptr;
1692
1693 if (IsIrqContext() || (uxQueueLength == 0U) || (uxItemSize == 0U))
1694 return nullptr;
1695
1696 if (uxQueueLength > stk::sync::MessageQueue::CAPACITY_MAX)
1697 return nullptr;
1698
1699 // Placement-new the FrtosQueue control block into the caller-supplied buffer.
1700 // Static assert guards against the buffer being too small.
1701 static_assert(sizeof(StaticQueue_t) >= sizeof(FrtosQueue),
1702 "StaticQueue_t is too small to hold FrtosQueue. "
1703 "Increase STATIC_QUEUE_TCB_SIZE_WORDS in freertos_stk.h.");
1704
1705 // Use the external-storage FrtosQueue constructor: no heap allocation for
1706 // either the control block or the data buffer.
1707 FrtosQueue *q = new (pxStaticQueue) FrtosQueue(
1708 static_cast<uint32_t>(uxQueueLength),
1709 static_cast<uint32_t>(uxItemSize),
1710 nullptr /* name */,
1711 pucQueueStorage);
1712
1713 q->m_cb_owned = false; // caller owns the memory; destructor must not delete
1714
1715 return static_cast<QueueHandle_t>(q);
1716}
1717
1719{
1720 if (xQueue == nullptr)
1721 return;
1722
1723 ObjFree(static_cast<FrtosQueue *>(xQueue));
1724}
1725
1727 const void *pvItemToQueue,
1728 TickType_t xTicksToWait)
1729{
1730 if ((xQueue == nullptr) || (pvItemToQueue == nullptr))
1731 return pdFAIL;
1732
1733 if (IsIrqContext() && (xTicksToWait != 0U))
1734 return pdFAIL;
1735
1736 FrtosQueue *q = static_cast<FrtosQueue *>(xQueue);
1737
1738 if (!q->m_mq.Put(pvItemToQueue, FrtosTimeoutToStk(xTicksToWait)))
1739 return pdFAIL;
1740
1741 QueueSetNotify(xQueue, q);
1742 return pdPASS;
1743}
1744
1746 const void *pvItemToQueue,
1747 TickType_t xTicksToWait)
1748{
1749 return xQueueSend(xQueue, pvItemToQueue, xTicksToWait);
1750}
1751
1753 const void *pvItemToQueue,
1754 TickType_t xTicksToWait)
1755{
1756 if ((xQueue == nullptr) || (pvItemToQueue == nullptr))
1757 return pdFAIL;
1758
1759 if (IsIrqContext() && (xTicksToWait != 0U))
1760 return pdFAIL;
1761
1762 FrtosQueue *q = static_cast<FrtosQueue *>(xQueue);
1763
1764 if (!q->m_mq.PutFront(pvItemToQueue, FrtosTimeoutToStk(xTicksToWait)))
1765 return pdFAIL;
1766
1767 QueueSetNotify(xQueue, q);
1768 return pdPASS;
1769}
1770
1772 void *pvBuffer,
1773 TickType_t xTicksToWait)
1774{
1775 if ((xQueue == nullptr) || (pvBuffer == nullptr))
1776 return pdFAIL;
1777
1778 if (IsIrqContext() && (xTicksToWait != 0U))
1779 return pdFAIL;
1780
1781 return static_cast<FrtosQueue *>(xQueue)->m_mq.Get(pvBuffer, FrtosTimeoutToStk(xTicksToWait))
1782 ? pdPASS : pdFAIL;
1783}
1784
1786 void *pvBuffer,
1787 TickType_t xTicksToWait)
1788{
1789 // Delegates to Peek(), which copies the oldest message without consuming
1790 // it. The operation is fully atomic and preserves queue ordering — no
1791 // Get + Put-back workaround required.
1792 if ((xQueue == nullptr) || (pvBuffer == nullptr))
1793 return pdFAIL;
1794
1795 if (IsIrqContext() && (xTicksToWait != 0U))
1796 return pdFAIL;
1797
1798 return static_cast<FrtosQueue *>(xQueue)->m_mq.Peek(
1799 pvBuffer, FrtosTimeoutToStk(xTicksToWait))
1800 ? pdPASS : pdFAIL;
1801}
1802
1804 void *pvBuffer)
1805{
1806 // Delegates to TryPeek() (= Peek(NO_WAIT)), which is ISR-safe and copies
1807 // the oldest message atomically without removing it.
1808 if ((xQueue == nullptr) || (pvBuffer == nullptr))
1809 return pdFAIL;
1810
1811 return static_cast<FrtosQueue *>(xQueue)->m_mq.TryPeek(pvBuffer) ? pdPASS : pdFAIL;
1812}
1813
1815{
1816 if (xQueue == nullptr)
1817 return 0U;
1818
1819 return static_cast<UBaseType_t>(
1820 static_cast<FrtosQueue *>(xQueue)->m_mq.GetCount());
1821}
1822
1824{
1825 // GetCount() is ISR-safe on targets where a size_t-aligned read is atomic
1826 // (per the STK MessageQueue documentation).
1827 if (xQueue == nullptr)
1828 return 0U;
1829
1830 return static_cast<UBaseType_t>(static_cast<FrtosQueue *>(xQueue)->m_mq.GetCount());
1831}
1832
1834{
1835 if (xQueue == nullptr)
1836 return 0U;
1837
1838 return static_cast<UBaseType_t>(
1839 static_cast<FrtosQueue *>(xQueue)->m_mq.GetSpace());
1840}
1841
1843{
1844 if (xQueue == nullptr)
1845 return pdFAIL;
1846
1847 static_cast<FrtosQueue *>(xQueue)->m_mq.Reset();
1848 return pdPASS;
1849}
1850
1851BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void *pvItemToQueue)
1852{
1853 // Mailbox (length-1 queue) overwrite pattern.
1854 // Reset() atomically discards any existing item and wakes blocked producers,
1855 // guaranteeing TryPut() will always find a free slot immediately after.
1856 if ((xQueue == nullptr) || (pvItemToQueue == nullptr))
1857 return pdFAIL;
1858
1859 FrtosQueue *q = static_cast<FrtosQueue *>(xQueue);
1860 q->m_mq.Reset();
1861 q->m_mq.TryPut(pvItemToQueue);
1862 QueueSetNotify(xQueue, q);
1863
1864 return pdPASS;
1865}
1866
1868 const void *pvItemToQueue,
1869 BaseType_t *pxHigherPriorityTaskWoken)
1870{
1871 // ISR-safe variant of xQueueOverwrite. Reset() and TryPut() are both
1872 // ISR-safe per the STK MessageQueue contract.
1873 if ((xQueue == nullptr) || (pvItemToQueue == nullptr))
1874 return pdFAIL;
1875
1876 FrtosQueue *q = static_cast<FrtosQueue *>(xQueue);
1877 q->m_mq.Reset();
1878 q->m_mq.TryPut(pvItemToQueue);
1879 QueueSetNotify(xQueue, q);
1880
1881 if (pxHigherPriorityTaskWoken != nullptr)
1882 *pxHigherPriorityTaskWoken = pdFALSE;
1883
1884 return pdPASS;
1885}
1886
1888 const void *pvItemToQueue,
1889 BaseType_t *pxHigherPriorityTaskWoken)
1890{
1891 if ((xQueue == nullptr) || (pvItemToQueue == nullptr))
1892 return pdFAIL;
1893
1894 FrtosQueue *q = static_cast<FrtosQueue *>(xQueue);
1895 bool ok = q->m_mq.TryPut(pvItemToQueue);
1896
1897 if (ok)
1898 QueueSetNotify(xQueue, q);
1899
1900 // STK handles the wake-up internally; the wrapper does not need to
1901 // request an explicit yield from ISR because SWRR re-evaluates on the
1902 // next tick. Set the flag to pdFALSE to avoid spurious portYIELD_FROM_ISR.
1903 if (pxHigherPriorityTaskWoken != nullptr)
1904 *pxHigherPriorityTaskWoken = pdFALSE;
1905
1906 return ok ? pdPASS : pdFAIL;
1907}
1908
1910 void *pvBuffer,
1911 BaseType_t *pxHigherPriorityTaskWoken)
1912{
1913 if ((xQueue == nullptr) || (pvBuffer == nullptr))
1914 return pdFAIL;
1915
1916 bool ok = static_cast<FrtosQueue *>(xQueue)->m_mq.TryGet(pvBuffer);
1917
1918 if (pxHigherPriorityTaskWoken != nullptr)
1919 *pxHigherPriorityTaskWoken = pdFALSE;
1920
1921 return ok ? pdPASS : pdFAIL;
1922}
1923
1925 const void *pvItemToQueue,
1926 BaseType_t *pxHigherPriorityTaskWoken)
1927{
1928 // Send-to-back from ISR is identical to xQueueSendFromISR: TryPut()
1929 // appends to the back of the ring buffer.
1930 return xQueueSendFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken);
1931}
1932
1934 const void *pvItemToQueue,
1935 BaseType_t *pxHigherPriorityTaskWoken)
1936{
1937 if ((xQueue == nullptr) || (pvItemToQueue == nullptr))
1938 return pdFAIL;
1939
1940 FrtosQueue *q = static_cast<FrtosQueue *>(xQueue);
1941 bool ok = q->m_mq.TryPutFront(pvItemToQueue);
1942
1943 if (ok)
1944 QueueSetNotify(xQueue, q);
1945
1946 // STK handles the wake-up internally; set the flag to pdFALSE to avoid
1947 // spurious portYIELD_FROM_ISR.
1948 if (pxHigherPriorityTaskWoken != nullptr)
1949 *pxHigherPriorityTaskWoken = pdFALSE;
1950
1951 return ok ? pdPASS : pdFAIL;
1952}
1953
1955{
1956 // IsEmpty() reads m_count which is size_t-aligned; ISR-safe on targets
1957 // where such a read is atomic (per stk::sync::MessageQueue contract).
1958 if (xQueue == nullptr)
1959 return pdTRUE;
1960
1961 return static_cast<const FrtosQueue *>(xQueue)->m_mq.IsEmpty() ? pdTRUE : pdFALSE;
1962}
1963
1965{
1966 // IsFull() reads m_count and m_capacity; both are size_t-aligned and
1967 // m_capacity is const, so the read is ISR-safe on naturally-atomic targets
1968 // (per stk::sync::MessageQueue contract).
1969 if (xQueue == nullptr)
1970 return pdTRUE;
1971
1972 return static_cast<const FrtosQueue *>(xQueue)->m_mq.IsFull() ? pdTRUE : pdFALSE;
1973}
1974
1975// -----------------------------------------------------------------------------
1976// xQueueGetMutexHolder / xQueueGetMutexHolderFromISR
1977// -----------------------------------------------------------------------------
1978// FreeRTOS re-uses its internal queue struct for mutex semaphores, so these
1979// two functions exist as QueueHandle_t-typed aliases of the semaphore
1980// counterparts. In this STK wrapper the object types are distinct:
1981//
1982// FrtosQueue — wraps stk::sync::MessageQueue; no mutex, no owner.
1983// FrtosSemaphore — wraps stk::sync::Semaphore (Counting) or
1984// stk::sync::Mutex (Mutex kind).
1985//
1986// Type discrimination:
1987// The first byte at the handle address is read via GetSemKindFromHandle(),
1988// which returns SemKind::None for any byte that is not a known SemKind
1989// enumerator (0x80 = Counting, 0x81 = Mutex). FrtosQueue byte[0] is
1990// byte[0] of stk::sync::MessageQueue (an ITraceable vtable/data pointer)
1991// and is never 0x80 or 0x81 under normal linking, so GetSemKindFromHandle()
1992// reliably returns SemKind::None for plain queue handles.
1993//
1994// GetSemKindFromHandle() == SemKind::Mutex -> FrtosSemaphore (Mutex kind)
1995// GetSemKindFromHandle() != SemKind::Mutex -> FrtosQueue or other (no owner)
1996//
1997// If the handle is a FrtosSemaphore with SemKind::Mutex the call is forwarded
1998// to xSemaphoreGetMutexHolder[FromISR]() which reads Mutex::GetOwner().
1999// For a plain FrtosQueue, or for a counting/binary semaphore, NULL is returned
2000// because the owner concept does not apply to those object types.
2001//
2002// STK Mutex always supports priority inheritance; no additional bookkeeping is
2003// required here — GetOwner() already reflects the current holder.
2004// -----------------------------------------------------------------------------
2005
2006#if configUSE_MUTEXES
2007
2008// Helper: given a raw QueueHandle_t, return the FrtosSemaphore* if the handle
2009// is actually a mutex-kind semaphore, or nullptr otherwise.
2011{
2012 if (xQueue == nullptr)
2013 return nullptr;
2014
2015 // GetSemKindFromHandle() returns SemKind::None for FrtosQueue handles
2016 // (and any unrecognised object), SemKind::Counting or SemKind::Mutex for
2017 // FrtosSemaphore handles. Only SemKind::Mutex carries an owner field.
2018 if (GetSemKindFromHandle(xQueue) != SemKind::Mutex)
2019 return nullptr;
2020
2021 return static_cast<FrtosSemaphore *>(xQueue);
2022}
2023
2025{
2026 // Resolve to FrtosSemaphore (Mutex kind) or bail out.
2028 if (s == nullptr)
2029 return nullptr;
2030
2031 // Delegate to the semaphore variant which acquires a ScopedCriticalSection
2032 // to make the TId snapshot consistent with concurrent Unlock() calls.
2033 return xSemaphoreGetMutexHolder(static_cast<SemaphoreHandle_t>(xQueue));
2034}
2035
2037{
2038 // Resolve to FrtosSemaphore (Mutex kind) or bail out.
2040 if (s == nullptr)
2041 return nullptr;
2042
2043 // Delegate to the ISR-safe semaphore variant which reads GetOwner() via a
2044 // single pointer-sized atomic load — no additional critical section needed.
2045 return xSemaphoreGetMutexHolderFromISR(static_cast<SemaphoreHandle_t>(xQueue));
2046}
2047
2048#endif // configUSE_MUTEXES
2049
2050// ===========================================================================
2051// Queue Set API
2052//
2053// A queue set acts as a fan-in multiplexer: one task can block on multiple
2054// queues and/or binary/counting semaphores simultaneously, waking as soon as
2055// any member receives an item.
2056//
2057// Implementation model:
2058// FrtosQueueSet owns an internal stk::sync::MessageQueue whose payload
2059// element size is sizeof(void*). Whenever a member queue or semaphore
2060// successfully receives a new item it writes its own handle (a void*)
2061// into this FIFO via QueueSetNotify(). xQueueSelectFromSet() performs a
2062// blocking Get() from the same FIFO and returns the handle to the caller.
2063//
2064// FreeRTOS API contracts enforced here:
2065// - Members must not already belong to another set (asserted).
2066// - Members must be empty when removed from a set (asserted).
2067// - Mutexes must not be added to queue sets (FreeRTOS API restriction).
2068// - xEventQueueLength must be >= sum of all member capacities; the caller
2069// is responsible for sizing correctly. Overflow is silently dropped by
2070// TryPut() in QueueSetNotify(), matching the FreeRTOS reference behaviour.
2071//
2072// Restrictions (matching FreeRTOS):
2073// - xQueueOverwrite / xQueueOverwriteFromISR should not be used with queues
2074// that are members of a set, as the overwrite generates a set notification
2075// even when the old value is replaced rather than a new slot filled, which
2076// can produce spurious wakeups. This mirrors the documented FreeRTOS
2077// caveat.
2078// ===========================================================================
2079#if configUSE_QUEUE_SETS
2080
2082{
2083 if (IsIrqContext() || (uxEventQueueLength == 0U))
2084 return nullptr;
2085
2086 if (uxEventQueueLength > stk::sync::MessageQueue::CAPACITY_MAX)
2087 return nullptr;
2088
2089 FrtosQueueSet *qs = ObjAlloc<FrtosQueueSet>(uxEventQueueLength);
2090
2091 if ((qs == nullptr) || !qs->IsValid())
2092 {
2093 ObjFree(qs);
2094 return nullptr;
2095 }
2096
2097 return static_cast<QueueSetHandle_t>(qs);
2098}
2099
2101 QueueSetHandle_t xQueueSet)
2102{
2103 if ((xQueueOrSemaphore == nullptr) || (xQueueSet == nullptr))
2104 return pdFAIL;
2105
2106 FrtosQueueSet *qs = static_cast<FrtosQueueSet *>(xQueueSet);
2107
2108 // Type discrimination between FrtosQueue and FrtosSemaphore via
2109 // GetSemKindFromHandle(): returns SemKind::None for queues (and any
2110 // unrecognised handle), SemKind::Counting or SemKind::Mutex for semaphores.
2111 {
2112 const SemKind kind = GetSemKindFromHandle(xQueueOrSemaphore);
2113
2114 if (kind != SemKind::None)
2115 {
2116 // Semaphore or mutex path.
2117 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xQueueOrSemaphore);
2118
2119 // FreeRTOS API contract: mutexes cannot be queue set members.
2120 if (s->m_kind == SemKind::Mutex)
2121 return pdFAIL;
2122
2123 STK_ASSERT(s->m_set == nullptr); // must not already belong to a set
2124 if (s->m_set != nullptr)
2125 return pdFAIL;
2126
2127 STK_ASSERT(s->m_sem->GetCount() == 0U); // must be empty when added
2128 if (s->m_sem->GetCount() != 0U)
2129 return pdFAIL;
2130
2131 s->m_set = qs;
2132 }
2133 else
2134 {
2135 // Queue path.
2136 FrtosQueue *q = static_cast<FrtosQueue *>(xQueueOrSemaphore);
2137
2138 STK_ASSERT(q->m_set == nullptr); // must not already belong to a set
2139 if (q->m_set != nullptr)
2140 return pdFAIL;
2141
2142 STK_ASSERT(q->m_mq.IsEmpty()); // must be empty when added
2143 if (!q->m_mq.IsEmpty())
2144 return pdFAIL;
2145
2146 q->m_set = qs;
2147 }
2148 }
2149
2150 return pdPASS;
2151}
2152
2154 QueueSetHandle_t xQueueSet)
2155{
2156 if ((xQueueOrSemaphore == nullptr) || (xQueueSet == nullptr))
2157 return pdFAIL;
2158
2159 {
2160 const SemKind kind = GetSemKindFromHandle(xQueueOrSemaphore);
2161
2162 if (kind != SemKind::None)
2163 {
2164 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xQueueOrSemaphore);
2165
2166 // API contract: member must belong to this specific set.
2167 STK_ASSERT(s->m_set == static_cast<FrtosQueueSet *>(xQueueSet));
2168 if (s->m_set != static_cast<FrtosQueueSet *>(xQueueSet))
2169 return pdFAIL;
2170
2171 // API contract: semaphore must be empty when removed.
2172 STK_ASSERT(s->m_sem->GetCount() == 0U);
2173 if (s->m_sem->GetCount() != 0U)
2174 return pdFAIL;
2175
2176 s->m_set = nullptr;
2177 }
2178 else
2179 {
2180 FrtosQueue *q = static_cast<FrtosQueue *>(xQueueOrSemaphore);
2181
2182 STK_ASSERT(q->m_set == static_cast<FrtosQueueSet *>(xQueueSet));
2183 if (q->m_set != static_cast<FrtosQueueSet *>(xQueueSet))
2184 return pdFAIL;
2185
2186 // API contract: queue must be empty when removed.
2187 STK_ASSERT(q->m_mq.IsEmpty());
2188 if (!q->m_mq.IsEmpty())
2189 return pdFAIL;
2190
2191 q->m_set = nullptr;
2192 }
2193 }
2194
2195 return pdPASS;
2196}
2197
2199 TickType_t xTicksToWait)
2200{
2201 if (xQueueSet == nullptr)
2202 return nullptr;
2203
2204 if (IsIrqContext() && (xTicksToWait != 0U))
2205 return nullptr;
2206
2207 FrtosQueueSet *qs = static_cast<FrtosQueueSet *>(xQueueSet);
2208
2209 // Block until a member handle arrives in the token FIFO or timeout fires.
2210 void *handle = nullptr;
2211 if (!qs->m_token_mq->Get(&handle, FrtosTimeoutToStk(xTicksToWait)))
2212 return nullptr; // timeout
2213
2214 return static_cast<QueueSetMemberHandle_t>(handle);
2215}
2216
2218{
2219 if (xQueueSet == nullptr)
2220 return nullptr;
2221
2222 FrtosQueueSet *qs = static_cast<FrtosQueueSet *>(xQueueSet);
2223
2224 // Non-blocking: TryGet() is ISR-safe per the STK MessageQueue contract.
2225 void *handle = nullptr;
2226 if (!qs->m_token_mq->TryGet(&handle))
2227 return nullptr; // set is empty
2228
2229 return static_cast<QueueSetMemberHandle_t>(handle);
2230}
2231
2232#endif // configUSE_QUEUE_SETS
2233
2234// ===========================================================================
2235// Semaphore / Mutex API
2236// ===========================================================================
2237
2239{
2240 // Binary semaphore: max count = 1, initial count = 0.
2243 static_cast<uint16_t>(0U),
2244 static_cast<uint16_t>(1U));
2245
2246 if ((s == nullptr) || (s->m_sem == nullptr))
2247 {
2248 ObjFree(s);
2249 return nullptr;
2250 }
2251 return static_cast<SemaphoreHandle_t>(s);
2252}
2253
2255{
2256 if (pxSemaphoreBuffer == nullptr)
2257 return nullptr;
2258
2259 // Static assert guards against the buffer being too small.
2260 static_assert(sizeof(StaticSemaphore_t) >= sizeof(FrtosSemaphore),
2261 "StaticSemaphore_t is too small to hold FrtosSemaphore. "
2262 "Increase STATIC_SEMAPHORE_TCB_SIZE_WORDS in freertos_stk.h.");
2263
2264 // Placement-new the FrtosSemaphore control block into the caller-supplied
2265 // buffer. Binary semaphore: max count = 1, initial count = 0.
2266 // The inner stk::sync::Semaphore is a value type embedded inside
2267 // FrtosSemaphore, so no additional heap allocation is needed.
2268 FrtosSemaphore *s = new (pxSemaphoreBuffer) FrtosSemaphore(
2270 static_cast<uint16_t>(0U),
2271 static_cast<uint16_t>(1U));
2272
2273 if (s->m_sem == nullptr)
2274 {
2275 s->~FrtosSemaphore(); // clean up without freeing
2276 return nullptr;
2277 }
2278
2279 s->m_cb_owned = false; // caller owns the memory; destructor must not delete
2280
2281 return static_cast<SemaphoreHandle_t>(s);
2282}
2283
2284#if configUSE_COUNTING_SEMAPHORES
2285
2287 UBaseType_t uxInitialCount)
2288{
2289 if (uxMaxCount == 0U || uxInitialCount > uxMaxCount)
2290 return nullptr;
2291
2292 if (uxMaxCount > stk::sync::Semaphore::COUNT_MAX)
2294
2297 static_cast<uint16_t>(uxInitialCount),
2298 static_cast<uint16_t>(uxMaxCount));
2299
2300 if ((s == nullptr) || (s->m_sem == nullptr))
2301 {
2302 ObjFree(s);
2303 return nullptr;
2304 }
2305 return static_cast<SemaphoreHandle_t>(s);
2306}
2307
2309 UBaseType_t uxInitialCount,
2310 StaticSemaphore_t *pxSemaphoreBuffer)
2311{
2312 if (pxSemaphoreBuffer == nullptr)
2313 return nullptr;
2314
2315 if (uxMaxCount == 0U || uxInitialCount > uxMaxCount)
2316 return nullptr;
2317
2318 if (uxMaxCount > stk::sync::Semaphore::COUNT_MAX)
2320
2321 static_assert(sizeof(StaticSemaphore_t) >= sizeof(FrtosSemaphore),
2322 "StaticSemaphore_t is too small to hold FrtosSemaphore. "
2323 "Increase STATIC_SEMAPHORE_TCB_SIZE_WORDS in freertos_stk.h.");
2324
2325 FrtosSemaphore *s = new (pxSemaphoreBuffer) FrtosSemaphore(
2327 static_cast<uint16_t>(uxInitialCount),
2328 static_cast<uint16_t>(uxMaxCount));
2329
2330 if (s->m_sem == nullptr)
2331 {
2332 s->~FrtosSemaphore();
2333 return nullptr;
2334 }
2335
2336 s->m_cb_owned = false;
2337 return static_cast<SemaphoreHandle_t>(s);
2338}
2339
2340#endif // configUSE_COUNTING_SEMAPHORES
2341
2342#if configUSE_MUTEXES
2343
2345{
2348 static_cast<uint16_t>(0U),
2349 static_cast<uint16_t>(1U));
2350
2351 if ((s == nullptr) || (s->m_mtx == nullptr))
2352 {
2353 ObjFree(s);
2354 return nullptr;
2355 }
2356
2357 return static_cast<SemaphoreHandle_t>(s);
2358}
2359
2361{
2362 if (pxMutexBuffer == nullptr)
2363 return nullptr;
2364
2365 static_assert(sizeof(StaticSemaphore_t) >= sizeof(FrtosSemaphore),
2366 "StaticSemaphore_t is too small to hold FrtosSemaphore. "
2367 "Increase STATIC_SEMAPHORE_TCB_SIZE_WORDS in freertos_stk.h.");
2368
2369 FrtosSemaphore *s = new (pxMutexBuffer) FrtosSemaphore(
2371 static_cast<uint16_t>(0U),
2372 static_cast<uint16_t>(1U));
2373
2374 if (s->m_mtx == nullptr)
2375 {
2376 s->~FrtosSemaphore();
2377 return nullptr;
2378 }
2379
2380 s->m_cb_owned = false;
2381 return static_cast<SemaphoreHandle_t>(s);
2382}
2383
2385{
2386 // STK Mutex is always recursive.
2387 return xSemaphoreCreateMutex();
2388}
2389
2391{
2392 // STK Mutex is always recursive; identical to xSemaphoreCreateMutexStatic.
2393 return xSemaphoreCreateMutexStatic(pxMutexBuffer);
2394}
2395
2396#endif // configUSE_MUTEXES
2397
2399{
2400 if (xSemaphore == nullptr)
2401 return;
2402
2403 ObjFree(static_cast<FrtosSemaphore *>(xSemaphore));
2404}
2405
2407{
2408 if (xSemaphore == nullptr)
2409 return pdFAIL;
2410
2411 if (IsIrqContext() && (xTicksToWait != 0U))
2412 return pdFAIL;
2413
2414 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xSemaphore);
2415 stk::Timeout tmo = FrtosTimeoutToStk(xTicksToWait);
2416
2417 if (s->m_kind == SemKind::Mutex)
2418 {
2419#if configUSE_MUTEXES
2420 return s->m_mtx->TimedLock(tmo) ? pdPASS : pdFAIL;
2421#else
2422 return pdFAIL;
2423#endif
2424 }
2425 else
2426 return s->m_sem->Wait(tmo) ? pdPASS : pdFAIL;
2427}
2428
2430 BaseType_t *pxHigherPriorityTaskWoken)
2431{
2432 if (xSemaphore == nullptr)
2433 return pdFAIL;
2434
2435 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xSemaphore);
2436
2437 // Mutex take from ISR is not permitted — mirrors xSemaphoreGiveFromISR.
2438 if (s->m_kind == SemKind::Mutex)
2439 return pdFAIL;
2440
2441 // TryWait() is Wait(NO_WAIT): decrement count if > 0, return immediately.
2442 // ISR-safe per the STK Semaphore contract.
2443 bool ok = s->m_sem->TryWait();
2444
2445 // STK handles priority re-evaluation internally on the next tick.
2446 // Set pdFALSE to avoid spurious portYIELD_FROM_ISR, matching the
2447 // pattern used by xQueueReceiveFromISR and xSemaphoreGiveFromISR.
2448 if (pxHigherPriorityTaskWoken != nullptr)
2449 *pxHigherPriorityTaskWoken = pdFALSE;
2450
2451 return ok ? pdPASS : pdFAIL;
2452}
2453
2455{
2456 // STK Mutex is always recursive; identical to xSemaphoreTake.
2457 return xSemaphoreTake(xMutex, xTicksToWait);
2458}
2459
2461{
2462 if (xSemaphore == nullptr)
2463 return pdFAIL;
2464
2465 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xSemaphore);
2466
2467 if (s->m_kind == SemKind::Mutex)
2468 {
2469#if configUSE_MUTEXES
2470 s->m_mtx->Unlock();
2471 // Mutexes are not eligible for queue sets (FreeRTOS API contract),
2472 // so no QueueSetNotify call is needed here.
2473 return pdPASS;
2474#else
2475 return pdFAIL;
2476#endif
2477 }
2478 else
2479 {
2480 // Guard against overflow: TryWait + Signal pattern.
2482 return pdFAIL;
2483
2484 s->m_sem->Signal();
2485 QueueSetNotify(xSemaphore, s);
2486 return pdPASS;
2487 }
2488}
2489
2494
2496 BaseType_t *pxHigherPriorityTaskWoken)
2497{
2498 if (xSemaphore == nullptr)
2499 return pdFAIL;
2500
2501 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xSemaphore);
2502
2503 if (s->m_kind == SemKind::Mutex)
2504 return pdFAIL; // Mutex give from ISR is not permitted.
2505
2507 return pdFAIL;
2508
2509 s->m_sem->Signal();
2510 QueueSetNotify(xSemaphore, s);
2511
2512 if (pxHigherPriorityTaskWoken != nullptr)
2513 *pxHigherPriorityTaskWoken = pdFALSE;
2514
2515 return pdPASS;
2516}
2517
2519{
2520 if (xSemaphore == nullptr)
2521 return 0U;
2522
2523 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xSemaphore);
2524
2525 if (s->m_kind == SemKind::Counting)
2526 return static_cast<UBaseType_t>(s->m_sem->GetCount());
2527
2528#if configUSE_MUTEXES
2529 // For a mutex, count = 0 means locked, 1 means unlocked.
2530 return (s->m_mtx->GetOwner() == stk::TID_NONE) ? 1U : 0U;
2531#else
2532 return 0U;
2533#endif
2534}
2535
2536#if configUSE_MUTEXES
2537
2539{
2540 // Returns the task that currently owns the mutex, or NULL if the mutex
2541 // is unlocked or xMutex is not a mutex-kind semaphore.
2542 //
2543 // FreeRTOS documents this function as not ISR-safe and requiring the
2544 // scheduler to be running. We guard it with a ScopedCriticalSection
2545 // so that the TId snapshot is consistent: if Unlock() is executing
2546 // concurrently, we see either the old owner or TID_NONE, never a torn
2547 // pointer.
2548 //
2549 // TId -> TaskHandle_t: STK stores task pointers as TId values via
2550 // TId = static_cast<TId>(reinterpret_cast<uintptr_t>(task_ptr))
2551 // so the inverse is:
2552 // task_ptr = reinterpret_cast<FrtosTask *>(static_cast<uintptr_t>(tid))
2553 // TID_NONE maps to nullptr (TaskHandle_t == nullptr means "no owner").
2554 if (xMutex == nullptr)
2555 return nullptr;
2556
2557 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xMutex);
2558
2559 if (s->m_kind != SemKind::Mutex)
2560 return nullptr; // not a mutex — owner concept does not apply
2561
2563
2564 const stk::TId owner = s->m_mtx->GetOwner();
2565
2566 if (owner == stk::TID_NONE)
2567 return nullptr; // mutex is currently unlocked
2568
2569 return reinterpret_cast<TaskHandle_t>(static_cast<uintptr_t>(owner));
2570}
2571
2573{
2574 // ISR-safe variant. GetOwner() reads a single TId (pointer-sized, aligned)
2575 // which is an atomic read on all supported STK architectures, so no
2576 // ScopedCriticalSection is needed here beyond what the caller already holds.
2577 // We still validate the handle and the semaphore kind before touching the
2578 // mutex state.
2579 if (xMutex == nullptr)
2580 return nullptr;
2581
2582 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xMutex);
2583
2584 if (s->m_kind != SemKind::Mutex)
2585 return nullptr;
2586
2587 const stk::TId owner = s->m_mtx->GetOwner();
2588
2589 if (owner == stk::TID_NONE)
2590 return nullptr;
2591
2592 return reinterpret_cast<TaskHandle_t>(static_cast<uintptr_t>(owner));
2593}
2594
2595#endif // configUSE_MUTEXES
2596
2597// ===========================================================================
2598// Software Timer API [configUSE_TIMERS]
2599// ===========================================================================
2600
2601#if configUSE_TIMERS
2602
2603TimerHandle_t xTimerCreate(const char *pcTimerName,
2604 TickType_t xTimerPeriodInTicks,
2605 UBaseType_t uxAutoReload,
2606 void *pvTimerID,
2607 TimerCallbackFunction_t pxCallbackFunction)
2608{
2609 if (IsIrqContext() || (pxCallbackFunction == nullptr) || (xTimerPeriodInTicks == 0U))
2610 return nullptr;
2611
2613 return nullptr;
2614
2616 pcTimerName,
2617 xTimerPeriodInTicks,
2618 (uxAutoReload == pdTRUE),
2619 pvTimerID,
2620 pxCallbackFunction);
2621
2622 return static_cast<TimerHandle_t>(t);
2623}
2624
2625TimerHandle_t xTimerCreateStatic(const char *pcTimerName,
2626 TickType_t xTimerPeriodInTicks,
2627 UBaseType_t uxAutoReload,
2628 void *pvTimerID,
2629 TimerCallbackFunction_t pxCallbackFunction,
2630 StaticTimer_t *pxTimerBuffer)
2631{
2632 if (pxTimerBuffer == nullptr)
2633 return nullptr;
2634
2635 if (IsIrqContext() || (pxCallbackFunction == nullptr) || (xTimerPeriodInTicks == 0U))
2636 return nullptr;
2637
2639 return nullptr;
2640
2641 static_assert(sizeof(StaticTimer_t) >= sizeof(FrtosTimer),
2642 "StaticTimer_t is too small to hold FrtosTimer. "
2643 "Increase STATIC_TIMER_TCB_SIZE_WORDS in freertos_stk.h.");
2644
2645 // Placement-new the FrtosTimer into the caller-supplied buffer.
2646 FrtosTimer *t = new (pxTimerBuffer) FrtosTimer(
2647 pcTimerName,
2648 xTimerPeriodInTicks,
2649 (uxAutoReload == pdTRUE),
2650 pvTimerID,
2651 pxCallbackFunction);
2652
2653 t->m_cb_owned = false; // caller owns the memory; xTimerDelete must not delete it
2654
2655 return static_cast<TimerHandle_t>(t);
2656}
2657
2659{
2660 if (xTimer == nullptr)
2661 return pdFAIL;
2662
2663 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2664
2665 if ((g_TimerHost != nullptr) && t->IsActive())
2666 g_TimerHost->Stop(*t);
2667
2668 ObjFree(t);
2669 return pdPASS;
2670}
2671
2673{
2674 if (IsIrqContext() || (xTimer == nullptr) || (g_TimerHost == nullptr))
2675 return pdFAIL;
2676
2677 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2678
2679 uint32_t period = t->m_auto_reload
2680 ? static_cast<uint32_t>(t->m_period)
2681 : 0U; // 0 = one-shot (no reload period)
2682
2683 return g_TimerHost->Restart(*t, static_cast<uint32_t>(t->m_period), period)
2684 ? pdPASS : pdFAIL;
2685}
2686
2688{
2689 if (IsIrqContext() || (xTimer == nullptr) || (g_TimerHost == nullptr))
2690 return pdFAIL;
2691
2692 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2693
2694 if (!t->IsActive())
2695 return pdFAIL;
2696
2697 return g_TimerHost->Stop(*t) ? pdPASS : pdFAIL;
2698}
2699
2701{
2702 return xTimerStart(xTimer, xTicksToWait); // Restart restarts from now
2703}
2704
2706 TickType_t xNewPeriod,
2707 TickType_t xTicksToWait)
2708{
2709 if ((xTimer == nullptr) || (xNewPeriod == 0U))
2710 return pdFAIL;
2711
2712 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2713 t->m_period = xNewPeriod;
2714
2715 return xTimerStart(xTimer, xTicksToWait);
2716}
2717
2718// ===========================================================================
2719// Timer ISR API
2720//
2721// TimerHost::PushCommand() calls m_commands.Write(cmd, NO_WAIT), which maps to
2722// PipeT<TimerCommand, N>::Write(..., NO_WAIT). With NO_WAIT, Write() acquires
2723// a ScopedCriticalSection (interrupt-safe on all STK targets), checks for
2724// space, copies the command if available, and returns immediately without
2725// sleeping — making it unconditionally safe to call from an ISR.
2726//
2727// xTicksToWait is accepted for FreeRTOS API compatibility but always ignored.
2728// pxHigherPriorityTaskWoken is always set to pdFALSE: the tick task wakes
2729// itself when it drains the command queue, no manual yield is required.
2730//
2731// Each ISR function is a thin wrapper that:
2732// 1. Sets *pxHigherPriorityTaskWoken = pdFALSE.
2733// 2. Validates arguments (null handle, null host, zero period).
2734// 3. Delegates to the matching task-context function, which calls PushCommand.
2735// The IsIrqContext() guard in xTimerStart/Stop is bypassed here because
2736// ISR variants call PushCommand directly through g_TimerHost.
2737// ===========================================================================
2738
2740 BaseType_t *pxHigherPriorityTaskWoken)
2741{
2742 if (pxHigherPriorityTaskWoken != nullptr)
2743 *pxHigherPriorityTaskWoken = pdFALSE;
2744
2745 if ((xTimer == nullptr) || (g_TimerHost == nullptr))
2746 return pdFAIL;
2747
2748 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2749 uint32_t period = t->m_auto_reload ? static_cast<uint32_t>(t->m_period) : 0U;
2750
2751 return g_TimerHost->Restart(*t, static_cast<uint32_t>(t->m_period), period)
2752 ? pdPASS : pdFAIL;
2753}
2754
2756 BaseType_t *pxHigherPriorityTaskWoken)
2757{
2758 if (pxHigherPriorityTaskWoken != nullptr)
2759 *pxHigherPriorityTaskWoken = pdFALSE;
2760
2761 if ((xTimer == nullptr) || (g_TimerHost == nullptr))
2762 return pdFAIL;
2763
2764 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2765
2766 if (!t->IsActive())
2767 return pdFAIL;
2768
2769 return g_TimerHost->Stop(*t) ? pdPASS : pdFAIL;
2770}
2771
2773 BaseType_t *pxHigherPriorityTaskWoken)
2774{
2775 // Reset = Restart from now, same as xTimerStart.
2776 return xTimerStartFromISR(xTimer, pxHigherPriorityTaskWoken);
2777}
2778
2780 TickType_t xNewPeriod,
2781 BaseType_t *pxHigherPriorityTaskWoken)
2782{
2783 if (pxHigherPriorityTaskWoken != nullptr)
2784 *pxHigherPriorityTaskWoken = pdFALSE;
2785
2786 if ((xTimer == nullptr) || (xNewPeriod == 0U) || (g_TimerHost == nullptr))
2787 return pdFAIL;
2788
2789 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2790 t->m_period = xNewPeriod;
2791
2792 // Restart with the new period, taking effect immediately.
2793 uint32_t period = t->m_auto_reload ? static_cast<uint32_t>(xNewPeriod) : 0U;
2794
2795 return g_TimerHost->Restart(*t, static_cast<uint32_t>(xNewPeriod), period) ? pdPASS : pdFAIL;
2796}
2797
2798// ===========================================================================
2799// xTimerPendFunctionCall / xTimerPendFunctionCallFromISR
2800//
2801// Implementation strategy
2802// -----------------------
2803// Both functions write a PendCall value into g_PendCallPipe — a statically-
2804// allocated stk::sync::PipeT<PendCall, FREERTOS_STK_PEND_CALL_QUEUE_SIZE>.
2805// No heap allocation is performed.
2806//
2807// The singleton FrtosPendDrainer timer (stored in g_PendDrainerBuf) is
2808// (re-)started as a 1-tick auto-reload timer after every successful enqueue.
2809// On each tick it drains g_PendCallPipe to completion inside the TimerHost
2810// handler task — exactly where FreeRTOS's timer daemon would invoke the
2811// callbacks — then stops itself when the pipe is empty.
2812//
2813// Task-context variant (xTimerPendFunctionCall)
2814// ---------------------------------------------
2815// Uses PipeT::Write() with the caller-supplied xTicksToWait timeout, so
2816// the call can block if the pipe is momentarily full. Returns pdFAIL only
2817// if the timeout expires before a free slot becomes available.
2818//
2819// ISR-context variant (xTimerPendFunctionCallFromISR)
2820// ---------------------------------------------------
2821// Uses PipeT::TryWrite() (= Write with NO_WAIT), which acquires a
2822// ScopedCriticalSection internally and is unconditionally ISR-safe.
2823// No allocator call is made; pvPortMalloc ISR-reentrancy is not required.
2824// Returns pdFAIL immediately if the pipe is full.
2825//
2826// TimerHost initialization
2827// ------------------------
2828// EnsureTimerHost() is called from the task-context variant for the same
2829// reason as in xTimerCreate(): the API must work even if the application
2830// has not created any explicit timers. From ISR, the host must already be
2831// running (g_TimerHost == nullptr -> pdFAIL), matching FreeRTOS behaviour.
2832// ===========================================================================
2833
2835 void *pvParameter1,
2836 uint32_t ulParameter2,
2837 TickType_t xTicksToWait)
2838{
2839 // API contract: must not be called from ISR context.
2840 if (IsIrqContext())
2841 return pdFAIL;
2842
2843 if (xFunctionToPend == nullptr)
2844 return pdFAIL;
2845
2846 // Ensure the TimerHost (and hence the drainer's scheduling context) exists.
2848 return pdFAIL;
2849
2850 // Write the call record into the static pipe (blocking with timeout).
2851 // PipeT::Write() acquires a ScopedCriticalSection internally.
2852 const PendCall call = { xFunctionToPend, pvParameter1, ulParameter2 };
2853 if (!g_PendCallPipe.Write(call, FrtosTimeoutToStk(xTicksToWait)))
2854 return pdFAIL;
2855
2856 // (Re-)start the singleton drainer so it wakes within 1 tick.
2857 // EnsurePendDrainer() constructs the drainer on first call and
2858 // calls TimerHost::Restart() which is task-safe.
2860
2861 return pdPASS;
2862}
2863
2865 void *pvParameter1,
2866 uint32_t ulParameter2,
2867 BaseType_t *pxHigherPriorityTaskWoken)
2868{
2869 // STK wakes the tick task internally; no manual context switch needed.
2870 if (pxHigherPriorityTaskWoken != nullptr)
2871 *pxHigherPriorityTaskWoken = pdFALSE;
2872
2873 if (xFunctionToPend == nullptr)
2874 return pdFAIL;
2875
2876 // From ISR the TimerHost must already be running (a timer was created
2877 // before the ISR fired — the only realistic usage pattern).
2878 if (g_TimerHost == nullptr)
2879 return pdFAIL;
2880
2881 // Non-blocking enqueue: TryWrite() holds a ScopedCriticalSection internally
2882 // — unconditionally ISR-safe, no allocator call, no blocking.
2883 const PendCall call = { xFunctionToPend, pvParameter1, ulParameter2 };
2884 if (!g_PendCallPipe.TryWrite(call))
2885 return pdFAIL;
2886
2887 // Kick the drainer via ISR-safe TimerHost::Restart().
2889
2890 return pdPASS;
2891}
2892
2894{
2895 if (xTimer == nullptr)
2896 return pdFALSE;
2897
2898 return static_cast<FrtosTimer *>(xTimer)->IsActive() ? pdTRUE : pdFALSE;
2899}
2900
2902{
2903 if (xTimer == nullptr)
2904 return nullptr;
2905
2906 return static_cast<FrtosTimer *>(xTimer)->m_timer_id;
2907}
2908
2909void vTimerSetTimerID(TimerHandle_t xTimer, void *pvNewID)
2910{
2911 if (xTimer == nullptr)
2912 return;
2913
2914 static_cast<FrtosTimer *>(xTimer)->m_timer_id = pvNewID;
2915}
2916
2918{
2919 if (xTimer == nullptr)
2920 return nullptr;
2921
2922 return static_cast<FrtosTimer *>(xTimer)->m_name;
2923}
2924
2926{
2927 if (xTimer == nullptr)
2928 return 0U;
2929
2930 return static_cast<FrtosTimer *>(xTimer)->m_period;
2931}
2932
2934{
2935 if ((xTimer == nullptr) || (g_TimerHost == nullptr))
2936 return 0U;
2937
2938 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2939 return t->IsActive() ? static_cast<TickType_t>(t->GetDeadline()) : 0U;
2940}
2941
2942#endif // configUSE_TIMERS
2943
2944// ===========================================================================
2945// Event Group API [configUSE_EVENT_GROUPS]
2946// ===========================================================================
2947
2948#if configUSE_EVENT_GROUPS
2949
2950// Translate STK EventFlags result -> FreeRTOS bits representation.
2951//
2952// Success path: return the matched bits directly.
2953// Error path : return the caller-supplied snapshot of the flags word taken
2954// immediately after the timeout was confirmed. FreeRTOS documents
2955// that xEventGroupWaitBits() returns the flags value *at the time
2956// of timeout*, not zero, so callers can distinguish which bits were
2957// set even though the wait condition was not fully satisfied.
2958// Pass snapshot = 0U for call sites where a timeout return of 0
2959// is the correct contract (e.g. xEventGroupSync).
2960static inline EventBits_t StkFlagsToFrtos(uint32_t result, EventBits_t snapshot = 0U)
2961{
2963 return snapshot; // timeout or parameter error — return flags snapshot
2964
2965 return static_cast<EventBits_t>(result);
2966}
2967
2968// Build STK EventFlags options from FreeRTOS xWaitForAllBits / xClearOnExit.
2969static inline uint32_t BuildStkFlagsOpts(BaseType_t xClearOnExit,
2970 BaseType_t xWaitForAllBits)
2971{
2973
2974 if (xWaitForAllBits == pdTRUE)
2976
2977 if (xClearOnExit == pdFALSE)
2979
2980 return opts;
2981}
2982
2984{
2985 if (IsIrqContext())
2986 return nullptr;
2987
2989 return static_cast<EventGroupHandle_t>(eg);
2990}
2991
2993{
2994 if (pxEventGroupBuffer == nullptr)
2995 return nullptr;
2996
2997 if (IsIrqContext())
2998 return nullptr;
2999
3000 static_assert(sizeof(StaticEventGroup_t) >= sizeof(FrtosEventGroup),
3001 "StaticEventGroup_t is too small to hold FrtosEventGroup. "
3002 "Increase STATIC_EVENT_GROUP_TCB_SIZE_WORDS in freertos_stk.h.");
3003
3004 FrtosEventGroup *eg = new (pxEventGroupBuffer) FrtosEventGroup();
3005 eg->m_cb_owned = false; // caller owns the memory; vEventGroupDelete must not delete it
3006
3007 return static_cast<EventGroupHandle_t>(eg);
3008}
3009
3011{
3012 if (xEventGroup == nullptr)
3013 return;
3014
3015 ObjFree(static_cast<FrtosEventGroup *>(xEventGroup));
3016}
3017
3019 EventBits_t uxBitsToSet)
3020{
3021 if (xEventGroup == nullptr)
3022 return 0U;
3023
3024 uint32_t result = static_cast<FrtosEventGroup *>(xEventGroup)->m_ef.Set(uxBitsToSet);
3025 return StkFlagsToFrtos(result);
3026}
3027
3029 EventBits_t uxBitsToClear)
3030{
3031 if (xEventGroup == nullptr)
3032 return 0U;
3033
3034 // STK Clear() returns the value BEFORE clearing - matches FreeRTOS contract.
3035 uint32_t prev = static_cast<FrtosEventGroup *>(xEventGroup)->m_ef.Clear(uxBitsToClear);
3036 return StkFlagsToFrtos(prev);
3037}
3038
3040{
3041 if (xEventGroup == nullptr)
3042 return 0U;
3043
3044 return static_cast<EventBits_t>(static_cast<FrtosEventGroup *>(xEventGroup)->m_ef.Get());
3045}
3046
3048 EventBits_t uxBitsToWaitFor,
3049 BaseType_t xClearOnExit,
3050 BaseType_t xWaitForAllBits,
3051 TickType_t xTicksToWait)
3052{
3053 if (IsIrqContext() || (xEventGroup == nullptr) || (uxBitsToWaitFor == 0U))
3054 return 0U;
3055
3056 stk::sync::EventFlags &ef = static_cast<FrtosEventGroup *>(xEventGroup)->m_ef;
3057
3058 uint32_t opts = BuildStkFlagsOpts(xClearOnExit, xWaitForAllBits);
3059 uint32_t result = ef.Wait(static_cast<uint32_t>(uxBitsToWaitFor),
3060 opts,
3061 FrtosTimeoutToStk(xTicksToWait));
3062
3063 // On timeout, FreeRTOS documents that the return value is the flags word
3064 // at the moment the timeout occurred, not zero. ef.Get() is a volatile
3065 // read that is atomic on all supported 32-bit STK targets; no CS needed.
3066 const EventBits_t snapshot = static_cast<EventBits_t>(ef.Get());
3067 return StkFlagsToFrtos(result, snapshot);
3068}
3069
3071 EventBits_t uxBitsToSet,
3072 BaseType_t *pxHigherPriorityTaskWoken)
3073{
3074 if (xEventGroup == nullptr)
3075 return pdFAIL;
3076
3077 uint32_t result = static_cast<FrtosEventGroup *>(xEventGroup)->m_ef.Set(
3078 static_cast<uint32_t>(uxBitsToSet));
3079
3080 if (pxHigherPriorityTaskWoken != nullptr)
3081 *pxHigherPriorityTaskWoken = pdFALSE;
3082
3083 return stk::sync::EventFlags::IsError(result) ? pdFAIL : pdPASS;
3084}
3085
3087 EventBits_t uxBitsToClear)
3088{
3089 if (xEventGroup == nullptr)
3090 return 0U;
3091
3092 uint32_t prev = static_cast<FrtosEventGroup *>(xEventGroup)->m_ef.Clear(
3093 static_cast<uint32_t>(uxBitsToClear));
3094 return StkFlagsToFrtos(prev);
3095}
3096
3097// -----------------------------------------------------------------------------
3098// xEventGroupSync
3099//
3100// Design rationale
3101// ----------------
3102// FreeRTOS xEventGroupSync() is a multi-task rendezvous (barrier):
3103//
3104// 1. Atomically set this task's "I'm ready" bits.
3105// 2. Wait (with AND semantics) until ALL bits in uxBitsToWaitFor are set.
3106// 3. On success, atomically clear uxBitsToWaitFor and return the snapshot
3107// taken just before the clear.
3108//
3109// The set-then-wait sequence must appear atomic from every other task's point
3110// of view, so that no task can wake from step 2 before every peer has
3111// completed step 1.
3112//
3113// STK provides this guarantee naturally:
3114// - EventFlags::Set() acquires a ScopedCriticalSection, ORs the bits,
3115// calls ConditionVariable::NotifyAll() (which reschedules waiters but
3116// cannot actually run them until the CS is released), then exits the CS.
3117// - EventFlags::Wait() enters its own ScopedCriticalSection and loops,
3118// re-checking the predicate on every wakeup.
3119//
3120// Because both operations hold the same type of CS and the scheduler cannot
3121// pre-empt inside one, the sequence "set my bit -> enter wait loop" is
3122// indivisible: a waiter woken by our Set() will observe our bits already
3123// present when it re-evaluates its own predicate.
3124//
3125// Auto-clear policy
3126// -----------------
3127// The first task to find the AND predicate satisfied will attempt to clear
3128// uxBitsToWaitFor. Subsequent tasks that already passed the predicate (or
3129// are woken in the same tick) receive the snapshot value that was current
3130// when they sampled the flags (before any clear). This matches FreeRTOS
3131// behaviour: every unblocked task sees the full set of sync bits in its
3132// return value, and the clear is performed by the internal Wait() path under
3133// the critical section, making it race-free across concurrent waiters.
3134//
3135// If OPT_WAIT_ALL + (default) clear-on-exit are passed together to
3136// EventFlags::Wait(), the STK implementation clears exactly the matched
3137// bits (== uxBitsToWaitFor) when the predicate is first satisfied by each
3138// waiter independently — which is safe because all tasks wait for the same
3139// superset mask, and STK's "m_flags &= ~matched" inside Wait() is idempotent
3140// once the bits are already 0.
3141// -----------------------------------------------------------------------------
3142
3144 EventBits_t uxBitsToSet,
3145 EventBits_t uxBitsToWaitFor,
3146 TickType_t xTicksToWait)
3147{
3148 // Contract checks that mirror the FreeRTOS reference implementation.
3149 if (xEventGroup == nullptr) return 0U;
3150 if (uxBitsToWaitFor == 0U) return 0U;
3151 if (IsIrqContext()) return 0U; // blocking wait is ISR-unsafe
3152
3153 FrtosEventGroup *eg = static_cast<FrtosEventGroup *>(xEventGroup);
3154 stk::sync::EventFlags &ef = eg->m_ef;
3155
3156 // Step 1: Set this task's rendezvous bit(s).
3157 //
3158 // uxBitsToSet == 0 is legal (observer role: task waits without contributing
3159 // a bit). Only call Set() when there is actually something to set.
3160 if (uxBitsToSet != 0U)
3161 {
3162 uint32_t set_result = ef.Set(static_cast<uint32_t>(uxBitsToSet));
3163
3164 // Treat an ERROR_PARAMETER return as a hard usage fault — the caller
3165 // violated the API (e.g. set bit 31).
3167
3168 if (stk::sync::EventFlags::IsError(set_result))
3169 return 0U;
3170 }
3171
3172 // Step 2 + 3: Wait for ALL bits in uxBitsToWaitFor, clear them on success.
3173 //
3174 // OPT_WAIT_ALL — AND semantics: all bits must be set simultaneously.
3175 // OPT_NO_CLEAR is NOT passed — matched bits are cleared atomically inside
3176 // Wait() when the predicate is first satisfied, exactly matching the
3177 // FreeRTOS "clear bits on exit" contract for xEventGroupSync().
3178 const uint32_t opts = stk::sync::EventFlags::OPT_WAIT_ALL;
3179 // (OPT_NO_CLEAR absent -> clear on success)
3180
3181 uint32_t result = ef.Wait(static_cast<uint32_t>(uxBitsToWaitFor),
3182 opts,
3183 FrtosTimeoutToStk(xTicksToWait));
3184
3185 // On timeout or error, return 0 (matches FreeRTOS reference behaviour).
3186 return StkFlagsToFrtos(result);
3187}
3188
3189#endif // configUSE_EVENT_GROUPS
3190
3191// ===========================================================================
3192// Task Notification API — indexed implementation [configUSE_TASK_NOTIFICATIONS]
3193//
3194// Each FrtosTask carries m_notify[configTASK_NOTIFICATION_ARRAY_ENTRIES], a
3195// fixed-size array of NotifySlot structs. Every slot is fully independent:
3196// - m_notify[i].value : 32-bit notification word (eSetBits / eIncrement / etc.)
3197// - m_notify[i].pending : set-without-overwrite guard
3198// - m_notify[i].sem : stk::sync::Semaphore (binary) used as the blocking
3199// primitive; the non-indexed API used this exact model
3200// for slot 0.
3201//
3202// Non-indexed functions (xTaskNotifyGive, ulTaskNotifyTake, xTaskNotify,
3203// xTaskNotifyWait, xTaskNotifyFromISR) are thin forwarding wrappers to slot 0.
3204//
3205// Out-of-range slot index: assertion in debug builds, early-return/pdFAIL in
3206// release builds — matching FreeRTOS configASSERT behaviour.
3207// ===========================================================================
3208
3209#if configUSE_TASK_NOTIFICATIONS
3210
3211// -----------------------------------------------------------------------------
3212// Internal helper: validate slot index and resolve a NULL handle to self.
3213// Returns the FrtosTask pointer on success, nullptr on failure.
3214// -----------------------------------------------------------------------------
3216{
3218
3220 return nullptr;
3221
3222 if (xTask == nullptr)
3223 xTask = xTaskGetCurrentTaskHandle();
3224
3225 return static_cast<FrtosTask *>(xTask);
3226}
3227
3228// -----------------------------------------------------------------------------
3229// Internal helper: apply a notification action to a slot under a held CS.
3230// Returns pdPASS / pdFAIL (mirrors the public xTaskNotify contract).
3231// -----------------------------------------------------------------------------
3233 uint32_t ulValue,
3234 eNotifyAction eAction)
3235{
3236 switch (eAction)
3237 {
3238 case eNoAction:
3239 break;
3240
3241 case eSetBits:
3242 slot.value |= ulValue;
3243 break;
3244
3245 case eIncrement:
3246 slot.value++;
3247 break;
3248
3250 slot.value = ulValue;
3251 break;
3252
3254 if (slot.pending)
3255 return pdFAIL; // value not consumed yet
3256 slot.value = ulValue;
3257 slot.pending = true;
3258 break;
3259
3260 default:
3261 return pdFAIL;
3262 }
3263
3264 return pdPASS;
3265}
3266
3267// ===========================================================================
3268// Indexed API
3269// ===========================================================================
3270
3272 UBaseType_t uxIndexToNotify)
3273{
3274 FrtosTask *t = ResolveNotifyTarget(xTaskToNotify, uxIndexToNotify);
3275 if (t == nullptr)
3276 return pdFAIL;
3277
3278 t->m_notify[uxIndexToNotify].sem.Signal(); // ISR-safe
3279 return pdPASS;
3280}
3281
3283 BaseType_t ulClearCountOnExit,
3284 TickType_t xTicksToWait)
3285{
3286 if (IsIrqContext())
3287 return 0U;
3288
3289 FrtosTask *t = ResolveNotifyTarget(nullptr, uxIndexToWait);
3290 if (t == nullptr)
3291 return 0U;
3292
3293 FrtosTask::NotifySlot &slot = t->m_notify[uxIndexToWait];
3294
3295 if (!slot.sem.Wait(FrtosTimeoutToStk(xTicksToWait)))
3296 return 0U;
3297
3298 uint32_t val = 0U;
3299
3300 {
3302 val = slot.value;
3303
3304 if (ulClearCountOnExit == pdTRUE)
3305 slot.value = 0U;
3306 else
3307 if (slot.value > 0U)
3308 slot.value--;
3309 }
3310
3311 return val + 1U; // +1: the semaphore signal itself counts
3312}
3313
3315 UBaseType_t uxIndexToNotify,
3316 uint32_t ulValue,
3317 eNotifyAction eAction)
3318{
3319 FrtosTask *t = ResolveNotifyTarget(xTaskToNotify, uxIndexToNotify);
3320 if (t == nullptr)
3321 return pdFAIL;
3322
3323 FrtosTask::NotifySlot &slot = t->m_notify[uxIndexToNotify];
3324 BaseType_t result = pdPASS;
3325
3326 {
3328 result = NotifyApplyAction(slot, ulValue, eAction);
3329 }
3330
3331 if (result == pdPASS)
3332 slot.sem.Signal();
3333
3334 return result;
3335}
3336
3338 uint32_t ulBitsToClearOnEntry,
3339 uint32_t ulBitsToClearOnExit,
3340 uint32_t *pulNotificationValue,
3341 TickType_t xTicksToWait)
3342{
3343 if (IsIrqContext())
3344 return pdFAIL;
3345
3346 FrtosTask *t = ResolveNotifyTarget(nullptr, uxIndexToWait);
3347 if (t == nullptr)
3348 return pdFAIL;
3349
3350 FrtosTask::NotifySlot &slot = t->m_notify[uxIndexToWait];
3351
3352 // Clear entry bits before blocking.
3353 {
3355 slot.value &= ~ulBitsToClearOnEntry;
3356 }
3357
3358 // Block until notified or timeout.
3359 if (!slot.sem.Wait(FrtosTimeoutToStk(xTicksToWait)))
3360 return pdFAIL;
3361
3362 // Read and apply exit-clear.
3363 {
3365
3366 if (pulNotificationValue != nullptr)
3367 *pulNotificationValue = slot.value;
3368
3369 slot.value &= ~ulBitsToClearOnExit;
3370 slot.pending = false;
3371 }
3372
3373 return pdPASS;
3374}
3375
3377 UBaseType_t uxIndexToNotify,
3378 uint32_t ulValue,
3379 eNotifyAction eAction,
3380 BaseType_t *pxHigherPriorityTaskWoken)
3381{
3382 BaseType_t result = xTaskNotifyIndexed(xTaskToNotify, uxIndexToNotify, ulValue, eAction);
3383
3384 if (pxHigherPriorityTaskWoken != nullptr)
3385 *pxHigherPriorityTaskWoken = pdFALSE;
3386
3387 return result;
3388}
3389
3390// ===========================================================================
3391// Non-indexed API — thin wrappers around slot 0
3392// ===========================================================================
3393
3395{
3396 return xTaskNotifyGiveIndexed(xTaskToNotify, 0U);
3397}
3398
3399uint32_t ulTaskNotifyTake(BaseType_t ulClearCountOnExit, TickType_t xTicksToWait)
3400{
3401 return ulTaskNotifyTakeIndexed(0U, ulClearCountOnExit, xTicksToWait);
3402}
3403
3405 uint32_t ulValue,
3406 eNotifyAction eAction)
3407{
3408 return xTaskNotifyIndexed(xTaskToNotify, 0U, ulValue, eAction);
3409}
3410
3411BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry,
3412 uint32_t ulBitsToClearOnExit,
3413 uint32_t *pulNotificationValue,
3414 TickType_t xTicksToWait)
3415{
3416 return xTaskNotifyWaitIndexed(0U, ulBitsToClearOnEntry, ulBitsToClearOnExit,
3417 pulNotificationValue, xTicksToWait);
3418}
3419
3421 uint32_t ulValue,
3422 eNotifyAction eAction,
3423 BaseType_t *pxHigherPriorityTaskWoken)
3424{
3425 return xTaskNotifyFromISRIndexed(xTaskToNotify, 0U, ulValue, eAction,
3426 pxHigherPriorityTaskWoken);
3427}
3428
3429// ===========================================================================
3430// Task Notification — AndQuery / StateClear / ValueClear extensions
3431//
3432// xTaskNotifyAndQuery[Indexed]
3433// Like xTaskNotify[Indexed] but additionally snapshots the slot value
3434// *before* the action is applied and writes it to *pulPreviousNotifyValue.
3435// This gives the caller an atomic read-modify-notify without a separate
3436// critical section in application code.
3437//
3438// xTaskNotifyAndQueryFromISR[Indexed]
3439// ISR-safe wrappers around the Indexed variant; pxHigherPriorityTaskWoken
3440// is always set to pdFALSE (STK handles the context switch internally).
3441//
3442// xTaskNotifyStateClear[Indexed]
3443// Clears the pending flag of the selected slot and drains the binary
3444// semaphore (one TryWait() is sufficient because max_count = 1).
3445// Returns pdTRUE when a notification was pending, pdFALSE otherwise.
3446// The notification value word is NOT modified — only the pending state.
3447//
3448// ulTaskNotifyValueClear[Indexed]
3449// Atomically clears the specified bits in slot.value (under a critical
3450// section) and returns the value *before* the clear. The pending flag
3451// and semaphore are not touched.
3452// ===========================================================================
3453
3454// -----------------------------------------------------------------------------
3455// xTaskNotifyAndQueryIndexed
3456// -----------------------------------------------------------------------------
3457
3459 UBaseType_t uxIndexToNotify,
3460 uint32_t ulValue,
3461 eNotifyAction eAction,
3462 uint32_t *pulPreviousNotifyValue)
3463{
3464 FrtosTask *t = ResolveNotifyTarget(xTaskToNotify, uxIndexToNotify);
3465 if (t == nullptr)
3466 return pdFAIL;
3467
3468 FrtosTask::NotifySlot &slot = t->m_notify[uxIndexToNotify];
3469 BaseType_t result = pdPASS;
3470
3471 {
3473
3474 // Snapshot the value *before* the action — this is the only difference
3475 // from xTaskNotifyIndexed.
3476 if (pulPreviousNotifyValue != nullptr)
3477 *pulPreviousNotifyValue = slot.value;
3478
3479 result = NotifyApplyAction(slot, ulValue, eAction);
3480 }
3481
3482 if (result == pdPASS)
3483 slot.sem.Signal();
3484
3485 return result;
3486}
3487
3488// -----------------------------------------------------------------------------
3489// xTaskNotifyAndQuery (slot 0 wrapper)
3490// -----------------------------------------------------------------------------
3491
3493 uint32_t ulValue,
3494 eNotifyAction eAction,
3495 uint32_t *pulPreviousNotifyValue)
3496{
3497 return xTaskNotifyAndQueryIndexed(xTaskToNotify, 0U, ulValue, eAction,
3498 pulPreviousNotifyValue);
3499}
3500
3501// -----------------------------------------------------------------------------
3502// xTaskNotifyAndQueryFromISRIndexed
3503// -----------------------------------------------------------------------------
3504
3506 UBaseType_t uxIndexToNotify,
3507 uint32_t ulValue,
3508 eNotifyAction eAction,
3509 uint32_t *pulPreviousNotifyValue,
3510 BaseType_t *pxHigherPriorityTaskWoken)
3511{
3512 BaseType_t result = xTaskNotifyAndQueryIndexed(xTaskToNotify, uxIndexToNotify,
3513 ulValue, eAction,
3514 pulPreviousNotifyValue);
3515
3516 if (pxHigherPriorityTaskWoken != nullptr)
3517 *pxHigherPriorityTaskWoken = pdFALSE; // STK handles the context switch internally
3518
3519 return result;
3520}
3521
3522// -----------------------------------------------------------------------------
3523// xTaskNotifyAndQueryFromISR (slot 0 wrapper)
3524// -----------------------------------------------------------------------------
3525
3527 uint32_t ulValue,
3528 eNotifyAction eAction,
3529 uint32_t *pulPreviousNotifyValue,
3530 BaseType_t *pxHigherPriorityTaskWoken)
3531{
3532 return xTaskNotifyAndQueryFromISRIndexed(xTaskToNotify, 0U, ulValue, eAction,
3533 pulPreviousNotifyValue,
3534 pxHigherPriorityTaskWoken);
3535}
3536
3537// -----------------------------------------------------------------------------
3538// xTaskNotifyStateClearIndexed
3539//
3540// Clears the pending notification state for the specified slot.
3541//
3542// FreeRTOS contract:
3543// - Returns pdTRUE if a notification was pending (slot was "notified").
3544// - Returns pdFALSE if no notification was pending.
3545// - The 32-bit value word is NOT modified; only the pending/signaled state.
3546//
3547// STK mapping:
3548// The "pending" state is represented by two cooperating members:
3549// slot.pending — set by eSetValueWithoutOverwrite; guards value overwrite.
3550// slot.sem — binary semaphore; its count > 0 means a notification
3551// arrived and has not yet been consumed by Wait/Take.
3552// Both are cleared here atomically under a critical section so that a
3553// concurrent xTaskNotifyWait or ulTaskNotifyTake cannot race with the clear.
3554//
3555// The semaphore is binary (max_count = 1), so one TryWait() is sufficient
3556// to drain it. We must not call sem.Wait() (blocking) from either task or
3557// ISR context — TryWait() (NO_WAIT, ISR-safe) is the correct choice here.
3558// -----------------------------------------------------------------------------
3559
3561 UBaseType_t uxIndexToClear)
3562{
3563 FrtosTask *t = ResolveNotifyTarget(xTask, uxIndexToClear);
3564 if (t == nullptr)
3565 return pdFALSE;
3566
3567 FrtosTask::NotifySlot &slot = t->m_notify[uxIndexToClear];
3568
3570
3571 // Determine whether a notification was pending before we clear anything.
3572 // A notification is considered "pending" when the semaphore has a count
3573 // (i.e. Signal() was called but Wait/Take has not yet consumed it) OR
3574 // when the value-without-overwrite guard flag is set.
3575 const bool was_pending = (slot.sem.GetCount() != 0U) || slot.pending;
3576
3577 if (was_pending)
3578 {
3579 // Drain the semaphore (binary, at most 1 token to consume).
3580 slot.sem.TryWait();
3581
3582 // Clear the eSetValueWithoutOverwrite pending guard so that a
3583 // subsequent xTaskNotify(eSetValueWithoutOverwrite) can write a new
3584 // value without being rejected.
3585 slot.pending = false;
3586 }
3587
3588 return was_pending ? pdTRUE : pdFALSE;
3589}
3590
3591// -----------------------------------------------------------------------------
3592// xTaskNotifyStateClear (slot 0 wrapper)
3593// -----------------------------------------------------------------------------
3594
3599
3600// -----------------------------------------------------------------------------
3601// ulTaskNotifyValueClearIndexed
3602//
3603// Atomically clears the specified bits in the slot's 32-bit value word and
3604// returns the value *before* the clear.
3605//
3606// FreeRTOS contract:
3607// - Performs: old = slot.value; slot.value &= ~ulBitsToClear; return old;
3608// - The pending flag and semaphore are NOT touched.
3609// - Pass ulBitsToClear = 0xFFFFFFFF to clear all bits.
3610// -----------------------------------------------------------------------------
3611
3613 UBaseType_t uxIndexToClear,
3614 uint32_t ulBitsToClear)
3615{
3616 FrtosTask *t = ResolveNotifyTarget(xTask, uxIndexToClear);
3617 if (t == nullptr)
3618 return 0U;
3619
3620 FrtosTask::NotifySlot &slot = t->m_notify[uxIndexToClear];
3621
3623
3624 const uint32_t prev = slot.value;
3625 slot.value &= ~ulBitsToClear;
3626
3627 return prev;
3628}
3629
3630// -----------------------------------------------------------------------------
3631// ulTaskNotifyValueClear (slot 0 wrapper)
3632// -----------------------------------------------------------------------------
3633
3635 uint32_t ulBitsToClear)
3636{
3637 return ulTaskNotifyValueClearIndexed(xTask, 0U, ulBitsToClear);
3638}
3639
3640#endif // configUSE_TASK_NOTIFICATIONS
3641
3642// ===========================================================================
3643// Thread-local storage (TLS) API
3644//
3645// Each FrtosTask carries a fixed-size m_tls[] array of void* slots
3646// (configNUM_THREAD_LOCAL_STORAGE_POINTERS entries, zero-initialised).
3647// The task to operate on is resolved via its TaskHandle_t, which is the
3648// FrtosTask* cast to void*. NULL resolves to the calling task via
3649// xTaskGetCurrentTaskHandle().
3650//
3651// STK TLS note:
3652// stk::hw::GetTlsPtr / SetTlsPtr manage the kernel's own per-task context
3653// pointer (used internally by the scheduler). Application TLS lives in
3654// the m_tls[] member of FrtosTask and does NOT touch the STK TP register,
3655// preserving the kernel's internal invariants.
3656// ===========================================================================
3657
3659 BaseType_t xIndex,
3660 void *pvValue)
3661{
3662 if (xIndex < 0 || static_cast<size_t>(xIndex) >= configNUM_THREAD_LOCAL_STORAGE_POINTERS)
3663 return;
3664
3665 // Resolve NULL -> calling task.
3666 if (xTaskToSet == nullptr)
3667 xTaskToSet = xTaskGetCurrentTaskHandle();
3668
3669 if (xTaskToSet == nullptr)
3670 return;
3671
3672 FrtosTask *t = static_cast<FrtosTask *>(xTaskToSet);
3673
3674 // A critical section is not strictly required for a pointer-sized store on
3675 // aligned memory on single-issue cores, but we guard anyway for strict
3676 // correctness on SMP targets (RP2040, dual-core Cortex-M33).
3678 t->m_tls[static_cast<size_t>(xIndex)] = pvValue;
3679}
3680
3682 BaseType_t xIndex)
3683{
3684 if (xIndex < 0 || static_cast<size_t>(xIndex) >= configNUM_THREAD_LOCAL_STORAGE_POINTERS)
3685 return nullptr;
3686
3687 // Resolve NULL -> calling task.
3688 if (xTaskToQuery == nullptr)
3689 xTaskToQuery = xTaskGetCurrentTaskHandle();
3690
3691 if (xTaskToQuery == nullptr)
3692 return nullptr;
3693
3694 const FrtosTask *t = static_cast<const FrtosTask *>(xTaskToQuery);
3695
3697 return t->m_tls[static_cast<size_t>(xIndex)];
3698}
3699
3700// ===========================================================================
3701// Stream Buffer API [configUSE_STREAM_BUFFERS]
3702//
3703// FrtosStreamBuffer wraps stk::sync::Pipe (element_size = 1), using it as a
3704// byte ring-buffer. sync::Pipe is preferred over sync::MessageQueue here
3705// because it provides WriteBulk / ReadBulk / TryWriteBulk / TryReadBulk
3706// natively, enabling efficient multi-byte transfers and clean trigger-level
3707// blocking without any single-byte Get() workarounds.
3708//
3709// Send -> Pipe::WriteBulk() (blocking) / TryWriteBulk() (ISR, NO_WAIT)
3710// Receive -> Pipe::ReadBulk() with trigger-level gating (see below)
3711// Pipe::TryReadBulk() (ISR, NO_WAIT)
3712//
3713// Trigger level (xStreamBufferReceive only):
3714// When m_trigger > 1 and the caller requested a blocking wait, Receive()
3715// calls ReadBulk(dst, m_trigger, timeout) to block atomically until at
3716// least m_trigger bytes are consumed, then TryReadBulk() drains the rest
3717// of the caller's buffer non-blocking. This reuses Pipe's own CV wait
3718// loop — no busy-spin, no lost-wakeup race, and no single-byte iteration.
3719// ===========================================================================
3720
3721#if configUSE_STREAM_BUFFERS
3722
3723static_assert(sizeof(StaticStreamBuffer_t) >= sizeof(FrtosStreamBuffer),
3724 "Increase STATIC_STREAM_BUFFER_TCB_SIZE_WORDS in FreeRTOS.h.");
3725
3727 size_t xTriggerLevelBytes)
3728{
3729 if (xBufferSizeBytes == 0U)
3730 return nullptr;
3731
3732 uint8_t *buf = ObjAllocArray<uint8_t>(xBufferSizeBytes);
3733 if (buf == nullptr)
3734 return nullptr;
3735
3736 FrtosStreamBuffer *sb = ObjAlloc<FrtosStreamBuffer>(buf, xBufferSizeBytes, xTriggerLevelBytes);
3737 if (sb == nullptr)
3738 {
3739 ObjFreeArray(buf);
3740 return nullptr;
3741 }
3742
3743 sb->m_buf_owned = true;
3744 sb->m_cb_owned = true;
3745
3746 return static_cast<StreamBufferHandle_t>(sb);
3747}
3748
3750 size_t xBufferSizeBytes,
3751 size_t xTriggerLevelBytes,
3752 uint8_t *pucStreamBufferStorageArea,
3753 StaticStreamBuffer_t *pxStaticStreamBuffer)
3754{
3755 if ((pucStreamBufferStorageArea == nullptr) ||
3756 (pxStaticStreamBuffer == nullptr) ||
3757 (xBufferSizeBytes == 0U))
3758 return nullptr;
3759
3760 static_assert(sizeof(StaticStreamBuffer_t) >= sizeof(FrtosStreamBuffer),
3761 "Increase STATIC_STREAM_BUFFER_TCB_SIZE_WORDS in FreeRTOS.h.");
3762
3763 FrtosStreamBuffer *sb = new (pxStaticStreamBuffer)
3764 FrtosStreamBuffer(pucStreamBufferStorageArea,
3765 xBufferSizeBytes,
3766 xTriggerLevelBytes);
3767
3768 // m_buf_owned = false, m_cb_owned = false already set by the ctor
3769 return static_cast<StreamBufferHandle_t>(sb);
3770}
3771
3773{
3774 if (xStreamBuffer == nullptr)
3775 return;
3776
3777 FrtosStreamBuffer *sb = static_cast<FrtosStreamBuffer *>(xStreamBuffer);
3778
3779 ObjFree(sb);
3780}
3781
3783 const void *pvTxData,
3784 size_t xDataLengthBytes,
3785 TickType_t xTicksToWait)
3786{
3787 if ((xStreamBuffer == nullptr) || (pvTxData == nullptr) || (xDataLengthBytes == 0U))
3788 return 0U;
3789
3790 FrtosStreamBuffer *sb = static_cast<FrtosStreamBuffer *>(xStreamBuffer);
3791
3792 const size_t sent = sb->m_pipe.WriteBulk(
3793 static_cast<const uint8_t *>(pvTxData),
3794 xDataLengthBytes,
3795 FrtosTimeoutToStk(xTicksToWait));
3796
3797 // Fire send-complete callback outside any critical section.
3798 if ((sent > 0U) && (sb->m_send_cb != nullptr))
3799 {
3800 BaseType_t woken = pdFALSE;
3801 sb->m_send_cb(xStreamBuffer, &woken);
3802 }
3803
3804 return sent;
3805}
3806
3808 const void *pvTxData,
3809 size_t xDataLengthBytes,
3810 BaseType_t *pxHigherPriorityTaskWoken)
3811{
3812 if (pxHigherPriorityTaskWoken != nullptr)
3813 *pxHigherPriorityTaskWoken = pdFALSE;
3814
3815 if ((xStreamBuffer == nullptr) || (pvTxData == nullptr) || (xDataLengthBytes == 0U))
3816 return 0U;
3817
3818 FrtosStreamBuffer *sb = static_cast<FrtosStreamBuffer *>(xStreamBuffer);
3819
3820 const size_t sent = sb->m_pipe.TryWriteBulk(
3821 static_cast<const uint8_t *>(pvTxData),
3822 xDataLengthBytes);
3823
3824 // Fire send-complete callback outside any critical section.
3825 // pxHigherPriorityTaskWoken is always pdFALSE per STK convention.
3826 if ((sent > 0U) && (sb->m_send_cb != nullptr))
3827 {
3828 BaseType_t woken = pdFALSE;
3829 sb->m_send_cb(xStreamBuffer, &woken);
3830 }
3831
3832 return sent;
3833}
3834
3836 void *pvRxData,
3837 size_t xBufferLengthBytes,
3838 TickType_t xTicksToWait)
3839{
3840 if ((xStreamBuffer == nullptr) || (pvRxData == nullptr) || (xBufferLengthBytes == 0U))
3841 return 0U;
3842
3843 if (IsIrqContext() && (xTicksToWait != 0U))
3844 return 0U;
3845
3846 FrtosStreamBuffer *sb = static_cast<FrtosStreamBuffer *>(xStreamBuffer);
3847
3848 // ReadBulkTriggered handles all cases in one call:
3849 // - NO_WAIT (xTicksToWait == 0): trigger not enforced, returns whatever
3850 // is available immediately (guaranteed by CV NO_WAIT fast-path).
3851 // - Blocking with trigger: waits until m_trigger bytes are present, then
3852 // drains up to xBufferLengthBytes in one atomic CS pass.
3853 // - Timeout before trigger: drains whatever arrived, possibly 0.
3854 const size_t total = sb->m_pipe.ReadBulkTriggered(
3855 static_cast<uint8_t *>(pvRxData),
3856 sb->m_trigger,
3857 xBufferLengthBytes,
3858 FrtosTimeoutToStk(xTicksToWait));
3859
3860 // Fire receive-complete callback outside any critical section.
3861 if ((total > 0U) && (sb->m_recv_cb != nullptr))
3862 {
3863 BaseType_t woken = pdFALSE;
3864 sb->m_recv_cb(xStreamBuffer, &woken);
3865 }
3866
3867 return total;
3868}
3869
3871 void *pvRxData,
3872 size_t xBufferLengthBytes,
3873 BaseType_t *pxHigherPriorityTaskWoken)
3874{
3875 if (pxHigherPriorityTaskWoken != nullptr)
3876 *pxHigherPriorityTaskWoken = pdFALSE;
3877
3878 if ((xStreamBuffer == nullptr) || (pvRxData == nullptr) || (xBufferLengthBytes == 0U))
3879 return 0U;
3880
3881 FrtosStreamBuffer *sb = static_cast<FrtosStreamBuffer *>(xStreamBuffer);
3882
3883 const size_t received = sb->m_pipe.TryReadBulk(
3884 static_cast<uint8_t *>(pvRxData),
3885 xBufferLengthBytes);
3886
3887 // Fire receive-complete callback outside any critical section.
3888 // pxHigherPriorityTaskWoken is always pdFALSE per STK convention.
3889 if ((received > 0U) && (sb->m_recv_cb != nullptr))
3890 {
3891 BaseType_t woken = pdFALSE;
3892 sb->m_recv_cb(xStreamBuffer, &woken);
3893 }
3894
3895 return received;
3896}
3897
3899{
3900 if (xStreamBuffer == nullptr)
3901 return 0U;
3902
3903 return static_cast<FrtosStreamBuffer *>(xStreamBuffer)->m_pipe.GetCount();
3904}
3905
3907{
3908 if (xStreamBuffer == nullptr)
3909 return 0U;
3910
3911 return static_cast<FrtosStreamBuffer *>(xStreamBuffer)->m_pipe.GetSpace();
3912}
3913
3915{
3916 return (xStreamBufferBytesAvailable(xStreamBuffer) == 0U) ? pdTRUE : pdFALSE;
3917}
3918
3920{
3921 return (xStreamBufferSpacesAvailable(xStreamBuffer) == 0U) ? pdTRUE : pdFALSE;
3922}
3923
3925{
3926 if (xStreamBuffer == nullptr)
3927 return pdFAIL;
3928
3929 static_cast<FrtosStreamBuffer *>(xStreamBuffer)->m_pipe.Reset();
3930 return pdPASS;
3931}
3932
3934 size_t xTriggerLevelBytes)
3935{
3936 if (xStreamBuffer == nullptr)
3937 return pdFALSE;
3938
3939 FrtosStreamBuffer *sb = static_cast<FrtosStreamBuffer *>(xStreamBuffer);
3940
3941 if (xTriggerLevelBytes > sb->m_pipe.GetCapacity())
3942 return pdFALSE;
3943
3945 sb->m_trigger = (xTriggerLevelBytes >= 1U ? xTriggerLevelBytes : 1U);
3946
3947 return pdTRUE;
3948}
3949
3951 BaseType_t *pxHigherPriorityTaskWoken)
3952{
3953 if (pxHigherPriorityTaskWoken != nullptr)
3954 *pxHigherPriorityTaskWoken = pdFALSE;
3955
3956 if (xStreamBuffer == nullptr)
3957 return pdFAIL;
3958
3959 // Pipe::Reset() acquires ScopedCriticalSection internally — ISR-safe.
3960 static_cast<FrtosStreamBuffer *>(xStreamBuffer)->m_pipe.Reset();
3961 return pdPASS;
3962}
3963
3965{
3966 if (xStreamBuffer == nullptr)
3967 return 0U;
3968
3969 // m_trigger is a plain size_t written only under ScopedCriticalSection
3970 // (in xStreamBufferSetTriggerLevel). A size_t-aligned read is atomic on
3971 // all Cortex-M targets — same rationale as Pipe::GetCount().
3972 return static_cast<const FrtosStreamBuffer *>(xStreamBuffer)->m_trigger;
3973}
3974
3975// -----------------------------------------------------------------------------
3976// xStreamBufferNextMessageLengthBytes
3977// -----------------------------------------------------------------------------
3978// A stream buffer carries an unframed byte sequence with no length-prefix
3979// headers, so there is no concept of a discrete "next message" boundary.
3980// The FreeRTOS reference implementation therefore defines
3981// xStreamBufferNextMessageLengthBytes() for stream buffers as identical to
3982// xStreamBufferBytesAvailable(): it returns the total number of bytes that
3983// could be consumed by the next xStreamBufferReceive() call without blocking.
3984//
3985// The function exists primarily so that code written against the combined
3986// stream/message buffer API can call the same introspection function on both
3987// handle types without a type-discriminating branch.
3988// -----------------------------------------------------------------------------
3989
3991{
3992 // Delegates to xStreamBufferBytesAvailable() which performs the NULL guard
3993 // and returns Pipe::GetCount() — ISR-safe on all supported targets.
3994 return xStreamBufferBytesAvailable(xStreamBuffer);
3995}
3996
3997// -----------------------------------------------------------------------------
3998// xStreamBufferCreateWithCallback / xStreamBufferCreateStaticWithCallback
3999//
4000// Identical to xStreamBufferCreate / xStreamBufferCreateStatic but store two
4001// optional per-instance notification callbacks (m_send_cb, m_recv_cb) in
4002// FrtosStreamBuffer. Callbacks fire at the end of every successful Send /
4003// Receive (including ISR variants), outside any critical section.
4004// -----------------------------------------------------------------------------
4005
4007 size_t xBufferSizeBytes,
4008 size_t xTriggerLevelBytes,
4009 StreamBufferCallbackFunction_t pxSendCompletedCallback,
4010 StreamBufferCallbackFunction_t pxReceiveCompletedCallback)
4011{
4012 if (xBufferSizeBytes == 0U)
4013 return nullptr;
4014
4015 uint8_t *buf = ObjAllocArray<uint8_t>(xBufferSizeBytes);
4016 if (buf == nullptr)
4017 return nullptr;
4018
4020 buf,
4021 xBufferSizeBytes,
4022 xTriggerLevelBytes,
4023 pxSendCompletedCallback,
4024 pxReceiveCompletedCallback);
4025
4026 if (sb == nullptr)
4027 {
4028 ObjFreeArray(buf);
4029 return nullptr;
4030 }
4031
4032 sb->m_buf_owned = true;
4033 sb->m_cb_owned = true;
4034
4035 return static_cast<StreamBufferHandle_t>(sb);
4036}
4037
4039 size_t xBufferSizeBytes,
4040 size_t xTriggerLevelBytes,
4041 uint8_t *pucStreamBufferStorageArea,
4042 StaticStreamBuffer_t *pxStaticStreamBuffer,
4043 StreamBufferCallbackFunction_t pxSendCompletedCallback,
4044 StreamBufferCallbackFunction_t pxReceiveCompletedCallback)
4045{
4046 if ((pucStreamBufferStorageArea == nullptr) ||
4047 (pxStaticStreamBuffer == nullptr) ||
4048 (xBufferSizeBytes == 0U))
4049 return nullptr;
4050
4051 static_assert(sizeof(StaticStreamBuffer_t) >= sizeof(FrtosStreamBuffer),
4052 "Increase STATIC_STREAM_BUFFER_TCB_SIZE_WORDS in FreeRTOS.h.");
4053
4054 FrtosStreamBuffer *sb = new (pxStaticStreamBuffer)
4055 FrtosStreamBuffer(pucStreamBufferStorageArea,
4056 xBufferSizeBytes,
4057 xTriggerLevelBytes,
4058 pxSendCompletedCallback,
4059 pxReceiveCompletedCallback);
4060 // m_buf_owned = false, m_cb_owned = false already set by ctor
4061 return static_cast<StreamBufferHandle_t>(sb);
4062}
4063
4064// ===========================================================================
4065// Message Buffer API
4066//
4067// FrtosMessageBuffer uses two STK primitives:
4068//
4069// m_pool (BlockMemoryPool) — payload blocks of AlignBlockSize(max_msg_size).
4070// m_eq (MessageQueue) — envelope FIFO: each slot holds a MsgEnvelope
4071// {size_t len; void* blk}.
4072//
4073// Send:
4074// 1. TimedAlloc() a block from m_pool (blocks if pool is empty).
4075// 2. memcpy payload into the block.
4076// 3. Put({len, blk}) envelope into m_eq (blocks if eq is full — which can
4077// only happen if TimedAlloc succeeded, i.e. m_pool and m_eq are always
4078// in sync: one envelope slot per pool block).
4079//
4080// Receive:
4081// 1. Get() envelope from m_eq (blocks if empty).
4082// 2. memcpy payload out of the block.
4083// 3. Free() the block back to m_pool — wakes one blocked sender if any.
4084//
4085// Reset:
4086// Drain all envelopes, free their blocks, then Reset() the eq.
4087// ===========================================================================
4088
4089static_assert(sizeof(StaticMessageBuffer_t) >= sizeof(FrtosMessageBuffer),
4090 "Increase STATIC_MESSAGE_BUFFER_TCB_SIZE_WORDS in FreeRTOS.h.");
4091
4092// Derive slot count from a flat byte budget.
4093static size_t MsgBufSlotCount(size_t budget_bytes, size_t max_msg_size)
4094{
4095 const size_t block_size = stk::memory::BlockMemoryPool::AlignBlockSize(max_msg_size);
4096 const size_t slot_cost = block_size + FrtosMessageBuffer::ENVELOPE_SIZE;
4097
4098 if (slot_cost == 0U)
4099 return 0U;
4100
4101 return budget_bytes / slot_cost;
4102}
4103
4105 size_t xMaxMessageSize)
4106{
4107 if ((xBufferSizeBytes == 0U) || (xMaxMessageSize == 0U))
4108 return nullptr;
4109
4110 const size_t count = MsgBufSlotCount(xBufferSizeBytes, xMaxMessageSize);
4111
4112 if (count == 0U)
4113 return nullptr;
4114
4115 FrtosMessageBuffer *mb = ObjAlloc<FrtosMessageBuffer>(xMaxMessageSize, count);
4116
4117 if (mb == nullptr)
4118 return nullptr;
4119
4120 if (!mb->m_pool.IsStorageValid() || !mb->m_eq.IsStorageValid())
4121 {
4122 ObjFreeRaw(mb);
4123 return nullptr;
4124 }
4125
4126 return static_cast<MessageBufferHandle_t>(mb);
4127}
4128
4130 size_t xMaxMessageSize,
4131 size_t xMessageCount,
4132 uint8_t *pucMessageBufferStorageArea,
4133 StaticMessageBuffer_t *pxStaticMessageBuffer)
4134{
4135 if ((pucMessageBufferStorageArea == nullptr) ||
4136 (pxStaticMessageBuffer == nullptr) ||
4137 (xMaxMessageSize == 0U) ||
4138 (xMessageCount == 0U))
4139 return nullptr;
4140
4141 const size_t block_size = stk::memory::BlockMemoryPool::AlignBlockSize(xMaxMessageSize);
4142 const size_t storage_size = xMessageCount * (block_size + FrtosMessageBuffer::ENVELOPE_SIZE);
4143
4144 FrtosMessageBuffer *mb = new (pxStaticMessageBuffer)
4145 FrtosMessageBuffer(xMaxMessageSize, xMessageCount,
4146 pucMessageBufferStorageArea, storage_size);
4147
4148 return static_cast<MessageBufferHandle_t>(mb);
4149}
4150
4151// -----------------------------------------------------------------------------
4152// xMessageBufferCreateWithCallback / xMessageBufferCreateStaticWithCallback
4153//
4154// Identical to xMessageBufferCreate / xMessageBufferCreateStatic but store two
4155// optional per-instance notification callbacks (m_send_cb, m_recv_cb) in
4156// FrtosMessageBuffer. Callbacks fire at the end of every successful Send /
4157// Receive (including ISR variants), outside any critical section.
4158// The handle passed to each callback is the MessageBufferHandle_t cast to
4159// StreamBufferHandle_t, matching the FreeRTOS convention for this callback type.
4160// -----------------------------------------------------------------------------
4161
4163 size_t xBufferSizeBytes,
4164 size_t xMaxMessageSize,
4165 StreamBufferCallbackFunction_t pxSendCompletedCallback,
4166 StreamBufferCallbackFunction_t pxReceiveCompletedCallback)
4167{
4168 if ((xBufferSizeBytes == 0U) || (xMaxMessageSize == 0U))
4169 return nullptr;
4170
4171 const size_t block_size = stk::memory::BlockMemoryPool::AlignBlockSize(xMaxMessageSize);
4172 const size_t slot_cost = block_size + FrtosMessageBuffer::ENVELOPE_SIZE;
4173 const size_t count = xBufferSizeBytes / slot_cost;
4174
4175 if (count == 0U)
4176 return nullptr;
4177
4179 xMaxMessageSize, count,
4180 pxSendCompletedCallback, pxReceiveCompletedCallback);
4181
4182 if (mb == nullptr)
4183 return nullptr;
4184
4185 // Detect heap failure in the envelope buffer allocation performed by the
4186 // heap constructor (m_eq is backed by a separately ObjAllocArray'd byte array).
4187 if (!mb->m_eq.IsStorageValid())
4188 {
4189 ObjFreeRaw(mb);
4190 return nullptr;
4191 }
4192
4193 return static_cast<MessageBufferHandle_t>(mb);
4194}
4195
4197 size_t xMaxMessageSize,
4198 size_t xMessageCount,
4199 uint8_t *pucMessageBufferStorageArea,
4200 StaticMessageBuffer_t *pxStaticMessageBuffer,
4201 StreamBufferCallbackFunction_t pxSendCompletedCallback,
4202 StreamBufferCallbackFunction_t pxReceiveCompletedCallback)
4203{
4204 if ((pucMessageBufferStorageArea == nullptr) ||
4205 (pxStaticMessageBuffer == nullptr) ||
4206 (xMaxMessageSize == 0U) ||
4207 (xMessageCount == 0U))
4208 return nullptr;
4209
4210 static_assert(sizeof(StaticMessageBuffer_t) >= sizeof(FrtosMessageBuffer),
4211 "Increase STATIC_MESSAGE_BUFFER_TCB_SIZE_WORDS in FreeRTOS.h.");
4212
4213 const size_t block_size = stk::memory::BlockMemoryPool::AlignBlockSize(xMaxMessageSize);
4214 const size_t storage_size = xMessageCount * (block_size + FrtosMessageBuffer::ENVELOPE_SIZE);
4215
4216 FrtosMessageBuffer *mb = new (pxStaticMessageBuffer)
4217 FrtosMessageBuffer(xMaxMessageSize, xMessageCount,
4218 pucMessageBufferStorageArea, storage_size,
4219 pxSendCompletedCallback, pxReceiveCompletedCallback);
4220
4221 return static_cast<MessageBufferHandle_t>(mb);
4222}
4223
4225{
4226 if (xMessageBuffer == nullptr)
4227 return;
4228
4229 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4230
4231 ObjFree(mb);
4232}
4233
4235 const void *pvTxData,
4236 size_t xDataLengthBytes,
4237 TickType_t xTicksToWait)
4238{
4239 if ((xMessageBuffer == nullptr) || (pvTxData == nullptr) || (xDataLengthBytes == 0U))
4240 return 0U;
4241
4242 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4243
4244 if (xDataLengthBytes > mb->m_max_msg_size)
4245 {
4246 STK_ASSERT(false); // API contract: message too large for this buffer
4247 return 0U;
4248 }
4249
4250 const stk::Timeout stk_timeout = FrtosTimeoutToStk(xTicksToWait);
4251
4252 // 1. Acquire a payload block (blocks until one is free or timeout).
4253 void *blk = mb->m_pool.TimedAlloc(stk_timeout);
4254 if (blk == nullptr)
4255 return 0U;
4256
4257 // 2. Copy payload into the block.
4258 STK_MEMCPY(blk, pvTxData, xDataLengthBytes);
4259
4260 // 3. Enqueue the envelope (should always succeed: pool and eq are 1:1,
4261 // but guard with NO_WAIT to avoid deadlock if they desync).
4262 FrtosMessageBuffer::MsgEnvelope env = { xDataLengthBytes, blk };
4263
4264 if (!mb->m_eq.Put(&env, stk::NO_WAIT))
4265 {
4266 // Envelope queue unexpectedly full — return block and report failure.
4267 STK_ASSERT(false);
4268 mb->m_pool.Free(blk);
4269 return 0U;
4270 }
4271
4272 // Fire send-complete callback outside any critical section.
4273 if (mb->m_send_cb != nullptr)
4274 {
4275 BaseType_t woken = pdFALSE;
4276 mb->m_send_cb(static_cast<StreamBufferHandle_t>(xMessageBuffer), &woken);
4277 }
4278
4279 return xDataLengthBytes;
4280}
4281
4283 void *pvRxData,
4284 size_t xBufferLengthBytes,
4285 TickType_t xTicksToWait)
4286{
4287 if ((xMessageBuffer == nullptr) || (pvRxData == nullptr) || (xBufferLengthBytes == 0U))
4288 return 0U;
4289
4290 if (IsIrqContext() && (xTicksToWait != 0U))
4291 return 0U;
4292
4293 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4294
4295 // 1. Dequeue the next envelope.
4296 FrtosMessageBuffer::MsgEnvelope env = { 0U, nullptr };
4297
4298 if (!mb->m_eq.Get(&env, FrtosTimeoutToStk(xTicksToWait)))
4299 return 0U;
4300
4301 // 2. Validate destination capacity.
4302 if (xBufferLengthBytes < env.len)
4303 {
4304 // Destination too small: re-enqueue the envelope so the message is
4305 // not lost (best-effort; if re-enqueue also fails the message is lost).
4306 STK_ASSERT(false); // API contract: pvRxData buffer too small
4307 mb->m_eq.TryPut(&env);
4308 return 0U;
4309 }
4310
4311 // 3. Copy payload out and return the block.
4312 STK_MEMCPY(pvRxData, env.blk, env.len);
4313 mb->m_pool.Free(env.blk);
4314
4315 // Fire receive-complete callback outside any critical section.
4316 if (mb->m_recv_cb != nullptr)
4317 {
4318 BaseType_t woken = pdFALSE;
4319 mb->m_recv_cb(static_cast<StreamBufferHandle_t>(xMessageBuffer), &woken);
4320 }
4321
4322 return env.len;
4323}
4324
4326{
4327 if (xMessageBuffer == nullptr)
4328 return pdTRUE;
4329
4330 return static_cast<FrtosMessageBuffer *>(xMessageBuffer)->m_eq.IsEmpty()
4331 ? pdTRUE : pdFALSE;
4332}
4333
4335{
4336 if (xMessageBuffer == nullptr)
4337 return pdTRUE;
4338
4339 return static_cast<FrtosMessageBuffer *>(xMessageBuffer)->m_pool.IsFull()
4340 ? pdTRUE : pdFALSE;
4341}
4342
4344{
4345 if (xMessageBuffer == nullptr)
4346 return 0U;
4347
4348 // Free pool blocks == available envelope slots (they are 1:1).
4349 return static_cast<FrtosMessageBuffer *>(xMessageBuffer)->m_pool.GetFreeCount();
4350}
4351
4353{
4354 if (xMessageBuffer == nullptr)
4355 return pdFAIL;
4356
4357 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4358
4359 // Drain all pending envelopes and return their blocks to the pool.
4360 FrtosMessageBuffer::MsgEnvelope env = { 0U, nullptr };
4361
4362 while (mb->m_eq.TryGet(&env))
4363 {
4364 if (env.blk != nullptr)
4365 mb->m_pool.Free(env.blk);
4366 }
4367
4368 // Reset the envelope queue (wakes any blocked senders).
4369 mb->m_eq.Reset();
4370
4371 return pdPASS;
4372}
4373
4374// ===========================================================================
4375// Message Buffer ISR / extended API
4376//
4377// xMessageBufferSendFromISR
4378// -------------------------
4379// Mirrors xMessageBufferSend() but is strictly non-blocking:
4380// 1. TimedAlloc(NO_WAIT) — grabs a pool block without blocking.
4381// BlockMemoryPool::TimedAlloc() with NO_WAIT uses a ScopedCriticalSection
4382// internally, making it ISR-safe on all STK targets.
4383// 2. memcpy payload into the block.
4384// 3. TryPut (= Put(NO_WAIT)) — enqueues the envelope without blocking.
4385// MessageQueue::Put(NO_WAIT) is also ScopedCriticalSection-guarded.
4386//
4387// If step 1 fails (pool empty) we return 0 immediately — no block was
4388// consumed so there is nothing to free. If step 3 fails (should not happen
4389// given pool/eq are 1:1) we release the block and return 0, matching the
4390// defensive pattern in xMessageBufferSend().
4391//
4392// pxHigherPriorityTaskWoken is always set to pdFALSE: STK's condition
4393// variable machinery reschedules waiting tasks via the kernel's own
4394// priority mechanism without requiring the ISR to request a context switch.
4395//
4396// xMessageBufferReceiveFromISR
4397// ----------------------------
4398// Uses TryGet (= Get(NO_WAIT)) on m_eq, which is ISR-safe. On success the
4399// envelope length is validated against the caller's buffer, the payload is
4400// copied, and the pool block is freed (Pool::Free() is also CS-guarded).
4401// On buffer-too-small the envelope is put back at the front so the message
4402// is not lost — TryPutFront (= PutFront(NO_WAIT)) retreats the tail pointer
4403// under the same CS, restoring the queue state exactly.
4404//
4405// xMessageBufferResetFromISR
4406// --------------------------
4407// Identical logic to xMessageBufferReset(); all underlying operations
4408// (TryGet, Pool::Free, MessageQueue::Reset) are already ISR-safe.
4409// pxHigherPriorityTaskWoken is always pdFALSE for the same reason as above.
4410//
4411// xMessageBufferNextLengthBytes
4412// -----------------------------
4413// Implements a non-destructive peek of the oldest envelope's length field
4414// using TryPeek(), which copies the envelope atomically without consuming it.
4415// No manual Get + PutFront pair or outer ScopedCriticalSection is needed:
4416// TryPeek() acquires its own internal CS and leaves m_tail and m_count
4417// unchanged. Returns 0 if the buffer is empty.
4418// ===========================================================================
4419
4421 const void *pvTxData,
4422 size_t xDataLengthBytes,
4423 BaseType_t *pxHigherPriorityTaskWoken)
4424{
4425 if (pxHigherPriorityTaskWoken != nullptr)
4426 *pxHigherPriorityTaskWoken = pdFALSE;
4427
4428 if ((xMessageBuffer == nullptr) || (pvTxData == nullptr) || (xDataLengthBytes == 0U))
4429 return 0U;
4430
4431 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4432
4433 if (xDataLengthBytes > mb->m_max_msg_size)
4434 {
4435 STK_ASSERT(false); // API contract: message exceeds max size
4436 return 0U;
4437 }
4438
4439 // Step 1: non-blocking pool allocation.
4440 void *blk = mb->m_pool.TimedAlloc(stk::NO_WAIT);
4441 if (blk == nullptr)
4442 return 0U; // pool full — no block consumed, nothing to free
4443
4444 // Step 2: copy payload.
4445 STK_MEMCPY(blk, pvTxData, xDataLengthBytes);
4446
4447 // Step 3: non-blocking enqueue.
4448 FrtosMessageBuffer::MsgEnvelope env = { xDataLengthBytes, blk };
4449 if (!mb->m_eq.TryPut(&env))
4450 {
4451 // Pool and eq are 1:1, so this should never happen. Defensively free
4452 // the block to avoid a pool leak and report failure.
4453 STK_ASSERT(false);
4454 mb->m_pool.Free(blk);
4455 return 0U;
4456 }
4457
4458 // Fire send-complete callback outside any critical section.
4459 // pxHigherPriorityTaskWoken is always pdFALSE per STK convention.
4460 if (mb->m_send_cb != nullptr)
4461 {
4462 BaseType_t woken = pdFALSE;
4463 mb->m_send_cb(static_cast<StreamBufferHandle_t>(xMessageBuffer), &woken);
4464 }
4465
4466 return xDataLengthBytes;
4467}
4468
4470 void *pvRxData,
4471 size_t xBufferLengthBytes,
4472 BaseType_t *pxHigherPriorityTaskWoken)
4473{
4474 if (pxHigherPriorityTaskWoken != nullptr)
4475 *pxHigherPriorityTaskWoken = pdFALSE;
4476
4477 if ((xMessageBuffer == nullptr) || (pvRxData == nullptr) || (xBufferLengthBytes == 0U))
4478 return 0U;
4479
4480 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4481
4482 // Step 1: non-blocking dequeue of the oldest envelope.
4483 FrtosMessageBuffer::MsgEnvelope env = { 0U, nullptr };
4484 if (!mb->m_eq.TryGet(&env))
4485 return 0U; // buffer empty
4486
4487 // Step 2: validate destination capacity.
4488 if (xBufferLengthBytes < env.len)
4489 {
4490 // Destination too small: put the envelope back at the front so the
4491 // message is not lost. TryPutFront retreats the tail pointer under
4492 // its own CS — identical to Get never having happened.
4493 STK_ASSERT(false); // API contract: pvRxData buffer too small
4494 mb->m_eq.TryPutFront(&env);
4495 return 0U;
4496 }
4497
4498 // Step 3: copy payload out and release pool block.
4499 STK_MEMCPY(pvRxData, env.blk, env.len);
4500 mb->m_pool.Free(env.blk);
4501
4502 // Fire receive-complete callback outside any critical section.
4503 // pxHigherPriorityTaskWoken is always pdFALSE per STK convention.
4504 if (mb->m_recv_cb != nullptr)
4505 {
4506 BaseType_t woken = pdFALSE;
4507 mb->m_recv_cb(static_cast<StreamBufferHandle_t>(xMessageBuffer), &woken);
4508 }
4509
4510 return env.len;
4511}
4512
4514 BaseType_t *pxHigherPriorityTaskWoken)
4515{
4516 if (pxHigherPriorityTaskWoken != nullptr)
4517 *pxHigherPriorityTaskWoken = pdFALSE;
4518
4519 if (xMessageBuffer == nullptr)
4520 return pdFAIL;
4521
4522 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4523
4524 // Drain all pending envelopes, returning every pool block before resetting
4525 // the queue. All three operations are ISR-safe (ScopedCriticalSection).
4526 FrtosMessageBuffer::MsgEnvelope env = { 0U, nullptr };
4527
4528 while (mb->m_eq.TryGet(&env))
4529 {
4530 if (env.blk != nullptr)
4531 mb->m_pool.Free(env.blk);
4532 }
4533
4534 // Reset the envelope queue; wakes any sender blocked on a full pool.
4535 mb->m_eq.Reset();
4536
4537 return pdPASS;
4538}
4539
4541{
4542 if (xMessageBuffer == nullptr)
4543 return 0U;
4544
4545 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4546
4547 // TryPeek copies the oldest envelope without consuming it. The internal
4548 // ScopedCriticalSection makes this ISR-safe; no outer CS is required.
4549 FrtosMessageBuffer::MsgEnvelope env = { 0U, nullptr };
4550
4551 if (!mb->m_eq.TryPeek(&env))
4552 return 0U; // buffer is empty — nothing to peek at
4553
4554 return env.len;
4555}
4556
4557#endif // configUSE_STREAM_BUFFERS
Collection of memory-related primitives (stk::memory namespace).
Top-level STK include. Provides the Kernel class template and all built-in task-switching strategies.
static __stk_forceinline void STK_MEMCPY(void *const dest, const void *const src, const size_t size)
A wrapper for a built-in memcpy, redefine to your own if required.
Definition stk_arch.h:540
#define STK_UNUSED(X)
Explicitly marks a variable as unused to suppress compiler warnings.
Definition stk_defs.h:608
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:409
#define __stk_weak
Marks a function or variable as weak, allowing it to be overridden by the user.
Definition stk_defs.h:198
Collection of synchronization primitives (stk::sync namespace).
Collection of time-related primitives (stk::time namespace).
static constexpr size_t StkGetWordCountForType()
void * malloc(size_t size)
void free(void *ptr)
static StkKernel g_StkKernel
static __stk_forceinline bool IsIrqContext()
static stk::time::TimerHost * g_TimerHost
static stk::Word g_TimerHostBuf[StkGetWordCountForType< stk::time::TimerHost >()]
FreeRTOS interface for SuperTinyKernel RTOS.
static FrtosKernel g_StkKernel
static FrtosSemaphore * QueueHandleAsMutex(QueueHandle_t xQueue)
static void ObjFreeRaw(T *obj)
static stk::sync::PipeT< PendCall, 8U > g_PendCallPipe
static void KickPendDrainerFromISR()
static stk::memory::MemoryAllocator::Stats s_MemStats(10240U)
static stk::Timeout FrtosTimeoutToStk(TickType_t t)
static int32_t FreertosStrcmp(const char str1[], const char str2[])
static bool EnsurePendDrainer()
static uint32_t BuildStkFlagsOpts(BaseType_t xClearOnExit, BaseType_t xWaitForAllBits)
static void QueueSetNotify(void *member_handle, THost *host)
static stk::time::TimerHost * g_TimerHost
#define FREERTOS_STK_MIN_STACK_WORDS
static constexpr size_t StkGetWordCountForType()
static stk::Word g_TimerHostBuf[StkGetWordCountForType< stk::time::TimerHost >()]
static int32_t FrtosPrioToStkWeight(UBaseType_t p)
static size_t MsgBufSlotCount(size_t budget_bytes, size_t max_msg_size)
static EventBits_t StkFlagsToFrtos(uint32_t result, EventBits_t snapshot=0U)
static void EnsureKernelInitialized()
static stk::Word g_PendDrainerBuf[StkGetWordCountForType< stk::time::TimerHost::Timer >()]
static SemKind GetSemKindFromHandle(const void *obj)
static stk::time::TimerHost::Timer * g_PendDrainer
void * malloc(size_t size)
static BaseType_t NotifyApplyAction(FrtosTask::NotifySlot &slot, uint32_t ulValue, eNotifyAction eAction)
stk::Kernel< stk::KERNEL_DYNAMIC|stk::KERNEL_SYNC|stk::KERNEL_TICKLESS, 16U, stk::SwitchStrategyFP32, stk::PlatformDefault > FrtosKernel
static void ObjFreeArray(void *ptr)
static void ObjFree(T *obj)
static UBaseType_t StkWeightToFrtosPrio(int32_t w)
static T * ObjAllocArray(size_t count)
static T * ObjAlloc(Args &&...args)
SemKind
@ Mutex
Mutex or recursive mutex (backed by stk::sync::Mutex).
@ None
Sentinel: not a FrtosSemaphore (e.g. plain FrtosQueue).
@ Counting
Binary or counting semaphore (backed by stk::sync::Semaphore).
static FrtosTask * ResolveNotifyTarget(TaskHandle_t xTask, UBaseType_t uxIndex)
void free(void *ptr)
BaseType_t xTaskCreateRestrictedStatic(const TaskParameters_restricted_t *pxTaskDefinition, TaskHandle_t *pxCreatedTask)
#define FREERTOS_STK_MAX_TASKS
Maximum number of concurrent tasks managed by the kernel. Increase if your application creates more t...
Definition FreeRTOS.h:208
void * StreamBufferHandle_t
Definition FreeRTOS.h:315
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken)
#define FREERTOS_STK_DEFAULT_STACK_WORDS
Default stack depth in Words when the caller passes usStackDepth = 0.
Definition FreeRTOS.h:215
BaseType_t xTimerStartFromISR(TimerHandle_t xTimer, BaseType_t *pxHigherPriorityTaskWoken)
BaseType_t xStreamBufferReset(StreamBufferHandle_t xStreamBuffer)
#define pdPASS
Definition FreeRTOS.h:265
void vTaskSuspendAll(void)
Suspend the scheduler (disables preemption; interrupts remain enabled).
#define taskSCHEDULER_NOT_STARTED
Definition FreeRTOS.h:438
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer)
StreamBufferHandle_t xStreamBufferCreate(size_t xBufferSizeBytes, size_t xTriggerLevelBytes)
size_t xPortGetFreeHeapSize(void)
#define taskSCHEDULER_SUSPENDED
Definition FreeRTOS.h:440
size_t xMessageBufferSpacesAvailable(MessageBufferHandle_t xMessageBuffer)
BaseType_t xTaskAbortDelay(TaskHandle_t xTask)
BaseType_t xQueuePeek(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)
eTaskState
Task execution state, returned by eTaskGetState().
Definition FreeRTOS.h:280
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken)
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)
BaseType_t xQueueSendToFront(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait)
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize)
BaseType_t xTaskNotifyAndQueryIndexed(TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue)
SemaphoreHandle_t xSemaphoreCreateBinary(void)
BaseType_t xTimerChangePeriod(TimerHandle_t xTimer, TickType_t xNewPeriod, TickType_t xTicksToWait)
size_t xMessageBufferNextLengthBytes(MessageBufferHandle_t xMessageBuffer)
void vPortFree(void *pv)
EventBits_t xEventGroupSync(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToSet, EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait)
void vTimerSetTimerID(TimerHandle_t xTimer, void *pvNewID)
QueueSetMemberHandle_t xQueueSelectFromSetFromISR(QueueSetHandle_t xQueueSet)
StreamBufferHandle_t xStreamBufferCreateWithCallback(size_t xBufferSizeBytes, size_t xTriggerLevelBytes, StreamBufferCallbackFunction_t pxSendCompletedCallback, StreamBufferCallbackFunction_t pxReceiveCompletedCallback)
TaskHandle_t xTaskGetCurrentTaskHandle(void)
BaseType_t xTaskNotifyFromISR(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t *pxHigherPriorityTaskWoken)
EventGroupHandle_t xEventGroupCreate(void)
void vPortEnterCritical(void)
BaseType_t xTimerStart(TimerHandle_t xTimer, TickType_t xTicksToWait)
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void)
Create a recursive mutex (same implementation as xSemaphoreCreateMutex).
void * QueueSetHandle_t
Definition FreeRTOS.h:319
void * SemaphoreHandle_t
Definition FreeRTOS.h:312
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken)
void vQueueDelete(QueueHandle_t xQueue)
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority)
StreamBufferHandle_t xStreamBufferCreateStaticWithCallback(size_t xBufferSizeBytes, size_t xTriggerLevelBytes, uint8_t *pucStreamBufferStorageArea, StaticStreamBuffer_t *pxStaticStreamBuffer, StreamBufferCallbackFunction_t pxSendCompletedCallback, StreamBufferCallbackFunction_t pxReceiveCompletedCallback)
BaseType_t xTimerStopFromISR(TimerHandle_t xTimer, BaseType_t *pxHigherPriorityTaskWoken)
void(* PendedFunction_t)(void *pvParameter1, uint32_t ulParameter2)
Definition FreeRTOS.h:328
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue, void *pvBuffer, BaseType_t *pxHigherPriorityTaskWoken)
BaseType_t xTimerReset(TimerHandle_t xTimer, TickType_t xTicksToWait)
long BaseType_t
Definition FreeRTOS.h:257
BaseType_t xMessageBufferReset(MessageBufferHandle_t xMessageBuffer)
uintptr_t StackType_t
Definition FreeRTOS.h:260
UBaseType_t uxTaskGetNumberOfTasks(void)
Return the number of tasks currently under kernel management.
BaseType_t xTaskNotifyStateClear(TaskHandle_t xTask)
BaseType_t xTaskNotifyIndexed(TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, uint32_t ulValue, eNotifyAction eAction)
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken)
void * EventGroupHandle_t
Definition FreeRTOS.h:314
MessageBufferHandle_t xMessageBufferCreateWithCallback(size_t xBufferSizeBytes, size_t xMaxMessageSize, StreamBufferCallbackFunction_t pxSendCompletedCallback, StreamBufferCallbackFunction_t pxReceiveCompletedCallback)
#define configTASK_NOTIFICATION_ARRAY_ENTRIES
Definition FreeRTOS.h:168
#define configUSE_MUTEXES
Definition FreeRTOS.h:176
QueueSetHandle_t xQueueCreateSet(UBaseType_t uxEventQueueLength)
UBaseType_t uxQueueSpacesAvailable(QueueHandle_t xQueue)
void vTaskStartScheduler(void)
TaskHandle_t xSemaphoreGetMutexHolderFromISR(SemaphoreHandle_t xMutex)
SemaphoreHandle_t xSemaphoreCreateCountingStatic(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer)
size_t xStreamBufferBytesAvailable(StreamBufferHandle_t xStreamBuffer)
TaskHandle_t xQueueGetMutexHolderFromISR(QueueHandle_t xQueue)
void(* TimerCallbackFunction_t)(TimerHandle_t xTimer)
Definition FreeRTOS.h:327
BaseType_t xTimerPendFunctionCall(PendedFunction_t xFunctionToPend, void *pvParameter1, uint32_t ulParameter2, TickType_t xTicksToWait)
UBaseType_t uxTaskGetSystemState(TaskStatus_t *pxTaskStatusArray, UBaseType_t uxArraySize, uint32_t *pulTotalRunTime)
BaseType_t xTaskNotifyStateClearIndexed(TaskHandle_t xTask, UBaseType_t uxIndexToClear)
TickType_t xTaskGetTickCount(void)
Return the tick count since the scheduler started.
UBaseType_t uxSemaphoreGetCount(SemaphoreHandle_t xSemaphore)
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup)
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken)
size_t xMessageBufferReceive(MessageBufferHandle_t xMessageBuffer, void *pvRxData, size_t xBufferLengthBytes, TickType_t xTicksToWait)
unsigned long UBaseType_t
Definition FreeRTOS.h:258
uint32_t ulTaskNotifyTake(BaseType_t ulClearCountOnExit, TickType_t xTicksToWait)
BaseType_t xTaskNotifyAndQueryFromISR(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue, BaseType_t *pxHigherPriorityTaskWoken)
void vEventGroupDelete(EventGroupHandle_t xEventGroup)
const char * pcTimerGetName(TimerHandle_t xTimer)
void * QueueSetMemberHandle_t
Definition FreeRTOS.h:320
void(* TaskFunction_t)(void *pvParameters)
Definition FreeRTOS.h:326
uint32_t EventBits_t
Definition FreeRTOS.h:380
#define portMAX_DELAY
Definition FreeRTOS.h:262
TaskHandle_t xSemaphoreGetMutexHolder(SemaphoreHandle_t xMutex)
BaseType_t xTaskDelayUntil(TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement)
BaseType_t xQueueIsQueueFullFromISR(const QueueHandle_t xQueue)
BaseType_t xStreamBufferIsFull(StreamBufferHandle_t xStreamBuffer)
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength, UBaseType_t uxItemSize, uint8_t *pucQueueStorage, StaticQueue_t *pxStaticQueue)
BaseType_t xTimerStop(TimerHandle_t xTimer, TickType_t xTicksToWait)
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer)
BaseType_t xTaskNotifyAndQuery(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue)
#define configUSE_QUEUE_SETS
Definition FreeRTOS.h:172
void * pvTaskGetThreadLocalStoragePointer(TaskHandle_t xTaskToQuery, BaseType_t xIndex)
BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait)
size_t xMessageBufferSendFromISR(MessageBufferHandle_t xMessageBuffer, const void *pvTxData, size_t xDataLengthBytes, BaseType_t *pxHigherPriorityTaskWoken)
BaseType_t xQueueIsQueueEmptyFromISR(const QueueHandle_t xQueue)
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify)
void vTaskDelay(TickType_t xTicksToDelay)
void vStreamBufferDelete(StreamBufferHandle_t xStreamBuffer)
MessageBufferHandle_t xMessageBufferCreateStatic(size_t xMaxMessageSize, size_t xMessageCount, uint8_t *pucMessageBufferStorageArea, StaticMessageBuffer_t *pxStaticMessageBuffer)
TimerHandle_t xTimerCreate(const char *pcTimerName, TickType_t xTimerPeriodInTicks, UBaseType_t uxAutoReload, void *pvTimerID, TimerCallbackFunction_t pxCallbackFunction)
void vTaskSuspend(TaskHandle_t xTaskToSuspend)
uint32_t ulTaskNotifyTakeIndexed(UBaseType_t uxIndexToWait, BaseType_t ulClearCountOnExit, TickType_t xTicksToWait)
BaseType_t xQueueRemoveFromSet(QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet)
SemaphoreHandle_t xSemaphoreCreateMutex(void)
TaskHandle_t xTaskGetHandle(const char *pcNameToQuery)
BaseType_t xStreamBufferResetFromISR(StreamBufferHandle_t xStreamBuffer, BaseType_t *pxHigherPriorityTaskWoken)
BaseType_t xStreamBufferSetTriggerLevel(StreamBufferHandle_t xStreamBuffer, size_t xTriggerLevelBytes)
BaseType_t xMessageBufferResetFromISR(MessageBufferHandle_t xMessageBuffer, BaseType_t *pxHigherPriorityTaskWoken)
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount)
void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet, BaseType_t xIndex, void *pvValue)
SemaphoreHandle_t xSemaphoreCreateRecursiveMutexStatic(StaticSemaphore_t *pxMutexBuffer)
BaseType_t xQueueAddToSet(QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet)
void taskYIELD_impl(void)
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS
Definition FreeRTOS.h:164
MessageBufferHandle_t xMessageBufferCreateStaticWithCallback(size_t xMaxMessageSize, size_t xMessageCount, uint8_t *pucMessageBufferStorageArea, StaticMessageBuffer_t *pxStaticMessageBuffer, StreamBufferCallbackFunction_t pxSendCompletedCallback, StreamBufferCallbackFunction_t pxReceiveCompletedCallback)
size_t xStreamBufferSpacesAvailable(StreamBufferHandle_t xStreamBuffer)
uint32_t ulTaskNotifyValueClearIndexed(TaskHandle_t xTask, UBaseType_t uxIndexToClear, uint32_t ulBitsToClear)
UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask)
UBaseType_t uxTaskPriorityGetFromISR(TaskHandle_t xTask)
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue, void *pvBuffer)
#define tskIDLE_PRIORITY
Definition FreeRTOS.h:2208
BaseType_t xStreamBufferIsEmpty(StreamBufferHandle_t xStreamBuffer)
BaseType_t xTaskGetSchedulerState(void)
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken)
BaseType_t xTaskNotifyFromISRIndexed(TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t *pxHigherPriorityTaskWoken)
#define pdFAIL
Definition FreeRTOS.h:266
#define configMAX_PRIORITIES
Definition FreeRTOS.h:152
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait)
void vTaskList(char *pcWriteBuffer)
EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t *pxEventGroupBuffer)
UBaseType_t uxQueueMessagesWaiting(QueueHandle_t xQueue)
BaseType_t xMessageBufferIsEmpty(MessageBufferHandle_t xMessageBuffer)
#define configTOTAL_HEAP_SIZE
Definition FreeRTOS.h:200
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait)
EventBits_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToClear)
#define pdFALSE
Definition FreeRTOS.h:264
void vMessageBufferDelete(MessageBufferHandle_t xMessageBuffer)
uint32_t TickType_t
Definition FreeRTOS.h:256
BaseType_t xQueueReset(QueueHandle_t xQueue)
BaseType_t xMessageBufferIsFull(MessageBufferHandle_t xMessageBuffer)
BaseType_t xTimerDelete(TimerHandle_t xTimer, TickType_t xTicksToWait)
eTaskState eTaskGetState(TaskHandle_t xTask)
void * TimerHandle_t
Definition FreeRTOS.h:313
BaseType_t xTaskNotifyAndQueryFromISRIndexed(TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue, BaseType_t *pxHigherPriorityTaskWoken)
void * QueueHandle_t
Definition FreeRTOS.h:311
TickType_t xTimerGetPeriod(TimerHandle_t xTimer)
size_t xMessageBufferReceiveFromISR(MessageBufferHandle_t xMessageBuffer, void *pvRxData, size_t xBufferLengthBytes, BaseType_t *pxHigherPriorityTaskWoken)
QueueSetMemberHandle_t xQueueSelectFromSet(QueueSetHandle_t xQueueSet, TickType_t xTicksToWait)
TickType_t xTimerGetExpiryTime(TimerHandle_t xTimer)
size_t xPortGetMinimumEverFreeHeapSize(void)
void(* StreamBufferCallbackFunction_t)(StreamBufferHandle_t xStreamBuffer, BaseType_t *pxHigherPriorityTaskWoken)
Definition FreeRTOS.h:336
void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement)
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToClear)
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore)
void vPortGetHeapStats(HeapStats_t *pxHeapStats)
MessageBufferHandle_t xMessageBufferCreate(size_t xBufferSizeBytes, size_t xMaxMessageSize)
BaseType_t xQueueSendToBack(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait)
BaseType_t xTimerIsTimerActive(TimerHandle_t xTimer)
void * pvPortMalloc(size_t xWantedSize)
BaseType_t xTaskCreateRestricted(const TaskParameters_restricted_t *pxTaskDefinition, TaskHandle_t *pxCreatedTask)
const char * pcTaskGetName(TaskHandle_t xTaskToQuery)
BaseType_t xTaskNotifyGiveIndexed(TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify)
size_t xStreamBufferSendFromISR(StreamBufferHandle_t xStreamBuffer, const void *pvTxData, size_t xDataLengthBytes, BaseType_t *pxHigherPriorityTaskWoken)
#define taskSCHEDULER_RUNNING
Definition FreeRTOS.h:439
TimerHandle_t xTimerCreateStatic(const char *pcTimerName, TickType_t xTimerPeriodInTicks, UBaseType_t uxAutoReload, void *pvTimerID, TimerCallbackFunction_t pxCallbackFunction, StaticTimer_t *pxTimerBuffer)
BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex)
TickType_t xTaskGetTickCountFromISR(void)
Return the tick count from ISR context (ISR-safe).
BaseType_t xTaskNotifyWaitIndexed(UBaseType_t uxIndexToWait, uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait)
TaskHandle_t xQueueGetMutexHolder(QueueHandle_t xQueue)
void vTaskResume(TaskHandle_t xTaskToResume)
UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask)
BaseType_t xTimerChangePeriodFromISR(TimerHandle_t xTimer, TickType_t xNewPeriod, BaseType_t *pxHigherPriorityTaskWoken)
UBaseType_t uxQueueMessagesWaitingFromISR(QueueHandle_t xQueue)
BaseType_t xTimerResetFromISR(TimerHandle_t xTimer, BaseType_t *pxHigherPriorityTaskWoken)
uint32_t ulTaskNotifyValueClear(TaskHandle_t xTask, uint32_t ulBitsToClear)
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToWaitFor, BaseType_t xClearOnExit, BaseType_t xWaitForAllBits, TickType_t xTicksToWait)
void * pvTimerGetTimerID(TimerHandle_t xTimer)
size_t xStreamBufferSend(StreamBufferHandle_t xStreamBuffer, const void *pvTxData, size_t xDataLengthBytes, TickType_t xTicksToWait)
BaseType_t xTaskResumeAll(void)
BaseType_t xTaskCreate(TaskFunction_t pvTaskCode, const char *pcName, uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask)
StreamBufferHandle_t xStreamBufferCreateStatic(size_t xBufferSizeBytes, size_t xTriggerLevelBytes, uint8_t *pucStreamBufferStorageArea, StaticStreamBuffer_t *pxStaticStreamBuffer)
size_t xStreamBufferGetTriggerLevel(StreamBufferHandle_t xStreamBuffer)
StackType_t uxTaskGetStackHighWaterMark2(TaskHandle_t xTask)
size_t xMessageBufferSend(MessageBufferHandle_t xMessageBuffer, const void *pvTxData, size_t xDataLengthBytes, TickType_t xTicksToWait)
#define configSTACK_DEPTH_TYPE
Definition FreeRTOS.h:271
BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex, TickType_t xTicksToWait)
void * MessageBufferHandle_t
Definition FreeRTOS.h:316
eNotifyAction
Action applied to a task's notification value by xTaskNotify().
Definition FreeRTOS.h:295
void vTaskGetRunTimeStats(char *pcWriteBuffer)
void * TaskHandle_t
Definition FreeRTOS.h:310
void vTaskEndScheduler(void)
End scheduling (KERNEL_DYNAMIC only). Included for API completeness.
void vPortExitCritical(void)
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToSet)
#define pdTRUE
Definition FreeRTOS.h:263
BaseType_t xTimerPendFunctionCallFromISR(PendedFunction_t xFunctionToPend, void *pvParameter1, uint32_t ulParameter2, BaseType_t *pxHigherPriorityTaskWoken)
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken)
BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction)
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)
size_t xStreamBufferReceiveFromISR(StreamBufferHandle_t xStreamBuffer, void *pvRxData, size_t xBufferLengthBytes, BaseType_t *pxHigherPriorityTaskWoken)
void vTaskDelete(TaskHandle_t xTaskToDelete)
size_t xStreamBufferReceive(StreamBufferHandle_t xStreamBuffer, void *pvRxData, size_t xBufferLengthBytes, TickType_t xTicksToWait)
BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void *pvItemToQueue)
size_t xStreamBufferNextMessageLengthBytes(StreamBufferHandle_t xStreamBuffer)
TaskHandle_t xTaskCreateStatic(TaskFunction_t pvTaskCode, const char *pcName, uint32_t ulStackDepth, void *pvParameters, UBaseType_t uxPriority, StackType_t *puxStackBuffer, StaticTask_t *pxTaskBuffer)
@ eRunning
Definition FreeRTOS.h:281
@ eReady
Definition FreeRTOS.h:282
@ eInvalid
Definition FreeRTOS.h:286
@ eDeleted
Definition FreeRTOS.h:285
@ eSuspended
Definition FreeRTOS.h:284
@ eIncrement
Definition FreeRTOS.h:298
@ eSetValueWithOverwrite
Definition FreeRTOS.h:299
@ eSetBits
Definition FreeRTOS.h:297
@ eNoAction
Definition FreeRTOS.h:296
@ eSetValueWithoutOverwrite
Definition FreeRTOS.h:300
Namespace of STK package.
uintptr_t Word
Native processor word type.
Definition stk_common.h:136
static void Yield()
Notify scheduler to switch to the next runnable task.
Definition stk_helper.h:402
SwitchStrategyFixedPriority< 32 > SwitchStrategyFP32
Shorthand alias for SwitchStrategyFixedPriority<32>: 32 priority levels (0..31), using a single 32-bi...
static void Sleep(Timeout tick_count)
Put calling process into a sleep state.
Definition stk_helper.h:358
static void SleepCancel(TId task_id)
Cancel sleep of the task.
Definition stk_helper.h:393
EAccessMode
Hardware access modes by the task.
Definition stk_common.h:42
@ ACCESS_PRIVILEGED
Privileged access mode (access to hardware is fully unrestricted).
Definition stk_common.h:44
constexpr Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:210
int64_t Ticks
Ticks value.
Definition stk_common.h:151
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:146
static bool SleepUntil(Ticks timestamp)
Put calling process into a sleep state until the specified timestamp.
Definition stk_helper.h:383
static Ticks GetTicks()
Get number of ticks elapsed since kernel start.
Definition stk_helper.h:313
PlatformArmCortexM PlatformDefault
Default platform implementation.
constexpr Timeout WAIT_INFINITE
Timeout value: block indefinitely until the synchronization object is signaled.
Definition stk_common.h:204
static TId GetTid()
Get task/thread Id of the calling task.
Definition stk_helper.h:237
constexpr TId TID_NONE
Reserved task/thread id representing zero/none thread id.
Definition stk_common.h:198
Word TId
Task (thread) id.
Definition stk_common.h:141
@ KERNEL_TICKLESS
Tickless mode. To use this mode STK_TICKLESS_IDLE must be defined to 1 in stk_config....
Definition stk_common.h:57
@ KERNEL_SYNC
Synchronization support (see Event).
Definition stk_common.h:56
@ KERNEL_DYNAMIC
Tasks can be added or removed and therefore exit when done.
Definition stk_common.h:54
bool IsInsideISR()
Check whether the CPU is currently executing inside a hardware interrupt service routine (ISR).
static void Free(void *ptr) __stk_weak
Free the memory chunk.
static void * Allocate(size_t size) __stk_weak
Allocate the memory chunk.
static Stats GetStats() __stk_weak
Get stats of memory allocation.
Snapshot of the memory allocator's statistics.
volatile size_t allocate_count
Number of succesful allocations with Allocate().
size_t GetAvailable() const
Get number of bytes currently available for allocation.
volatile size_t free_count
Number of succesful frees with Free().
volatile size_t min_ever_free
Minimum free bytes recorded since system start (watermark).
void RecordFree(size_t size)
Record sucessful deallocation.
Fixed-size block allocator with O(1) alloc/free and proper task-blocking semantics.
bool IsStorageValid() const
Verify that the backing storage is valid and the pool is ready for use.
bool Free(void *ptr)
Return a previously allocated block to the pool.
static constexpr size_t AlignBlockSize(size_t raw_size)
Round a raw block size up to the nearest multiple of BLOCK_ALIGN.
void * TimedAlloc(Timeout timeout_ticks=WAIT_INFINITE)
Allocate one block, blocking until one becomes available or the timeout expires.
Concrete implementation of IKernel.
Definition stk.h:85
static void Enter()
Enter a critical section.
static void Exit()
Exit a critical section.
RAII guard that enters the critical section on construction and exits it on destruction.
Definition stk_arch.h:363
virtual size_t GetStackSpace() const
Get available stack space.
Definition stk_common.h:336
Interface for a user task.
Definition stk_common.h:670
@ KSTATE_RUNNING
Initialized and running, IKernel::Start() was called successfully.
@ KSTATE_SUSPENDED
Scheduling is suspended with IKernelService::Suspend().
@ KSTATE_INACTIVE
Not ready, IKernel::Initialize() must be called.
RAII-style low-level synchronization primitive for atomic code execution. Used as building brick for ...
Definition stk_sync_cs.h:54
32-bit event flags group for multi-flag synchronization between tasks.
uint32_t Wait(uint32_t flags, uint32_t options=OPT_WAIT_ANY, Timeout timeout_ticks=WAIT_INFINITE)
Wait for one or more flags to be set.
uint32_t Get() const
Read the current flags word without modifying it.
static bool IsError(uint32_t result)
Checks if a return value from Set(), Clear(), or Wait() is an error.
static const uint32_t OPT_NO_CLEAR
Do not clear matched flags after a successful wait.
static const uint32_t OPT_WAIT_ALL
Wait for ALL of the specified flags to be set simultaneously (AND semantics).
static const uint32_t OPT_WAIT_ANY
Wait for ANY of the specified flags to be set (OR semantics, default).
uint32_t Set(uint32_t flags)
Set one or more flags.
Fixed-capacity, fixed-message-size FIFO queue for inter-task communication.
bool TryPeek(void *msg_ptr)
Attempt to peek at the next message without blocking.
bool IsEmpty() const
Check whether the queue is currently empty.
static const size_t CAPACITY_MAX
Max capacity supported (number of messages).
bool TryPutFront(const void *msg_ptr)
Attempt to put a message into the front of the queue without blocking.
bool TryPut(const void *msg_ptr)
Attempt to put a message into the back of the queue without blocking.
size_t GetSpace() const
Get the number of free slots currently available.
size_t GetCount() const
Get the current number of messages in the queue.
bool TryGet(void *msg_ptr)
Attempt to get a message from the queue without blocking.
bool PutFront(const void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Put a message into the front of the queue (LIFO / priority-insert order).
bool IsStorageValid() const
Verify that the backing storage is valid and the pool is ready for use.
bool Put(const void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Put a message into the back of the queue (FIFO order).
bool Get(void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Get a message from the queue.
void Reset()
Discard all messages and reset the queue to the empty state.
Recursive mutex primitive that allows the same thread to acquire the lock multiple times.
bool TimedLock(Timeout timeout_ticks)
Acquire lock.
TId GetOwner() const
Get owner of the mutex.
void Unlock() override
Release lock.
Thread-safe FIFO communication pipe for inter-task data passing.
size_t WriteBulk(const void *src, size_t count, Timeout timeout_ticks=WAIT_INFINITE)
Write multiple elements to the pipe.
size_t TryWriteBulk(const void *src, size_t count)
Attempt to write multiple elements to the pipe without blocking.
size_t GetCapacity() const
Get the maximum number of elements the pipe can hold.
size_t TryReadBulk(void *dst, size_t count)
Attempt to read multiple elements from the pipe without blocking.
size_t ReadBulkTriggered(void *dst, size_t trigger, size_t max_count, Timeout timeout_ticks=WAIT_INFINITE)
Read at least trigger elements, then drain up to max_count without blocking.
Thread-safe, type-safe FIFO communication pipe with internal storage.
Counting semaphore primitive for resource management and signaling.
void Signal()
Post a signal (increment counter).
uint16_t GetCount() const
Get current counter value.
static const uint16_t COUNT_MAX
Max count value supported.
bool Wait(Timeout timeout_ticks=WAIT_INFINITE)
Wait for a signal (decrement counter).
bool TryWait()
Poll the semaphore without blocking (decrement counter if available).
Software timer multiplexer that manages multiple Timer instances on top of a small fixed set of kerne...
bool Stop(Timer &tmr)
Stop running timer.
Abstract base class for a timer managed by TimerHost.
Ticks GetDeadline() const
Get the absolute time in ticks at which the timer will expire.
bool IsActive() const
Check whether the timer is currently active.
StackType_t * pxStackBase
Definition FreeRTOS.h:371
uint32_t xTaskNumber
Definition FreeRTOS.h:373
eTaskState eCurrentState
Definition FreeRTOS.h:367
TaskHandle_t xHandle
Definition FreeRTOS.h:365
StackType_t usStackHighWaterMark
Definition FreeRTOS.h:372
UBaseType_t uxBasePriority
Definition FreeRTOS.h:369
UBaseType_t uxCurrentPriority
Definition FreeRTOS.h:368
uint32_t ulRunTimeCounter
Definition FreeRTOS.h:370
const char * pcTaskName
Definition FreeRTOS.h:366
StackType_t * puxStackBuffer
Definition FreeRTOS.h:590
TaskFunction_t pvTaskCode
Definition FreeRTOS.h:585
StaticTask_t * pxTaskBuffer
Definition FreeRTOS.h:591
size_t xSizeOfSmallestFreeBlockInBytes
Definition FreeRTOS.h:2158
size_t xMinimumEverFreeBytesRemaining
Definition FreeRTOS.h:2160
size_t xNumberOfSuccessfulAllocations
Definition FreeRTOS.h:2161
size_t xSizeOfLargestFreeBlockInBytes
Definition FreeRTOS.h:2157
size_t xNumberOfFreeBlocks
Definition FreeRTOS.h:2159
size_t xNumberOfSuccessfulFrees
Definition FreeRTOS.h:2162
size_t xAvailableHeapSpaceInBytes
Definition FreeRTOS.h:2156
const char * m_name
TaskFunction_t m_func
uint32_t m_task_number
void Run() override
Entry point of the user task.
int32_t GetWeight() const override
Get static base weight of the task.
const stk::Word * GetStack() const override
Get pointer to the stack memory.
volatile int32_t m_weight
NotifySlot m_notify[1U]
stk::Word * m_stack
volatile State m_state
void OnDeadlineMissed(uint32_t) override
Called by the scheduler if deadline of the task is missed when Kernel is operating in Hard Real-Time ...
size_t m_stack_size
void OnExit() override
Called by the kernel before removal from the scheduling (see stk::KERNEL_DYNAMIC).
size_t GetStackHighWaterMark() const
size_t GetStackSize() const override
Get number of elements of the stack memory array.
void * m_tls[4U]
const char * GetTraceName() const override
Get task trace name set by application.
virtual ~FrtosTask()
static uint32_t s_task_counter
stk::EAccessMode GetAccessMode() const override
Get hardware access mode of the user task.
void * m_argument
volatile bool pending
true if a value was set but not yet consumed
STK_NONCOPYABLE_CLASS(NotifySlot)
stk::sync::Semaphore sem
binary semaphore: blocks the waiter, signaled by notifier
volatile uint32_t value
notification value word
stk::sync::MessageQueue m_mq
FrtosQueue(uint32_t cap, uint32_t msg_size, const char *name)
FrtosQueueSet * m_set
non-owning ptr to the queue set this member belongs to (nullptr if none)
static uint8_t * AllocBuffer(uint32_t cap, uint32_t msg_size)
FrtosQueue(uint32_t cap, uint32_t msg_size, const char *name, uint8_t *ext_buf)
stk::sync::Mutex * m_mtx
FrtosSemaphore(SemKind kind, uint16_t initial, uint16_t max_count)
stk::sync::Semaphore * m_sem
FrtosQueueSet * m_set
non-owning ptr to the queue set this member belongs to (nullptr if none)
bool m_cb_owned
true = heap-allocated, delete in ObjFree
stk::sync::MessageQueue * m_token_mq
FIFO of fired-member handles (void*).
FrtosQueueSet(UBaseType_t uxEventQueueLength)
uint8_t * m_buf
raw backing store for m_token_mq
bool IsValid() const
void * m_timer_id
TickType_t m_period
static bool EnsureTimerHost()
const char * m_name
FrtosTimer(const char *name, TickType_t period, bool auto_reload, void *timer_id, TimerCallbackFunction_t cb)
virtual ~FrtosTimer()
void OnExpired(stk::time::TimerHost *) override
Callback invoked by the handler task when this timer expires.
TimerCallbackFunction_t m_callback
PendedFunction_t func
void * param1
uint32_t param2
void OnExpired(stk::time::TimerHost *host) override
Callback invoked by the handler task when this timer expires.
stk::sync::EventFlags m_ef
stk::sync::Pipe m_pipe
byte ring-buffer (element_size = 1)
FrtosStreamBuffer(uint8_t *buf, size_t capacity, size_t trigger, StreamBufferCallbackFunction_t pSendCb=nullptr, StreamBufferCallbackFunction_t pRecvCb=nullptr)
StreamBufferCallbackFunction_t m_recv_cb
optional callback fired after a successful Receive
size_t m_trigger
minimum bytes before Receive() unblocks
bool m_cb_owned
true -> struct heap-allocated, deleted in vStreamBufferDelete
bool m_buf_owned
true -> data buffer heap-allocated, freed in dtor
StreamBufferCallbackFunction_t m_send_cb
optional callback fired after a successful Send
bool m_eq_buf_owned
true -> envelope buffer heap-allocated
static constexpr size_t ENVELOPE_SIZE
stk::memory::BlockMemoryPool m_pool
payload block allocator
FrtosMessageBuffer(size_t max_msg_size, size_t msg_count, uint8_t *storage, size_t storage_size, StreamBufferCallbackFunction_t pSendCb=nullptr, StreamBufferCallbackFunction_t pRecvCb=nullptr)
bool m_cb_owned
true -> struct heap-allocated
FrtosMessageBuffer(size_t max_msg_size, size_t msg_count, StreamBufferCallbackFunction_t pSendCb=nullptr, StreamBufferCallbackFunction_t pRecvCb=nullptr)
stk::sync::MessageQueue m_eq
envelope FIFO {len, blk}
StreamBufferCallbackFunction_t m_send_cb
optional callback fired after a successful Send
StreamBufferCallbackFunction_t m_recv_cb
optional callback fired after a successful Receive
size_t m_max_msg_size
max payload bytes per message
void * blk
pointer to block pool block holding the payload
size_t len
payload length in bytes