SuperTinyKernel™ RTOS 1.08.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.1"
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 (uses stk::sync::FastMutex) SemKind::Mutex (0x81)
565// - Recursive mutex (uses stk::sync::Mutex) SemKind::Mutex (0x81)
566//
567// Both mutex flavours share SemKind::Mutex; the concrete STK primitive is hidden behind FrtosMutexBase
568// (see below), so that the vptr lives in the mutex object and byte[0] of FrtosSemaphore stays the SemKind.
569//
570// The first byte of every FrtosSemaphore is its SemKind discriminant.
571// Call GetSemKindFromHandle() to safely classify an opaque handle.
572// ===========================================================================
573
574// SemKind — type discriminant stored as the first byte of every FrtosSemaphore.
575//
576// Values are chosen so they can never collide with byte[0] of a FrtosQueue or
577// any other FrtosXxx object:
578//
579// FrtosQueue byte[0] is byte[0] of stk::sync::MessageQueue, which inherits
580// from ITraceable. ITraceable starts with a vtable pointer (or equivalent
581// first data member); on all supported 32/64-bit targets that pointer value
582// is always in high memory (>= 4), so byte[0] is always >= 4 under normal
583// linking. Choosing 0x80 and 0x81 for the SemKind values places them well
584// above the 0x00–0x03 range previously used and well below any realistic
585// vtable-pointer low byte on little-endian targets, making the discriminant
586// robust even if future struct layout changes shift ITraceable internals.
587//
588// GetSemKindFromHandle() is the single authoritative function that reads and
589// validates the discriminant; all type-switching code must call it instead of
590// reading byte[0] directly.
591enum class SemKind : uint8_t
592{
593 None = 0x00U,
594 Counting = 0x80U,
595 Mutex = 0x81U,
596};
597
598// Return the SemKind discriminant for the object at \a obj, or SemKind::None
599// if the byte does not match any known SemKind value (i.e. the handle points
600// to a FrtosQueue or another non-semaphore object).
601//
602// The switch enumerates only the valid SemKind enumerators; any other byte
603// value falls through to the default and returns SemKind::None, making the
604// check both exhaustive and forward-safe.
605static SemKind GetSemKindFromHandle(const void *obj)
606{
607 const uint8_t first_byte = *static_cast<const uint8_t *>(obj);
608
609 switch (first_byte)
610 {
611 case static_cast<uint8_t>(SemKind::Counting): return SemKind::Counting;
612 case static_cast<uint8_t>(SemKind::Mutex): return SemKind::Mutex;
613 default: return SemKind::None;
614 }
615}
616
617#if configUSE_MUTEXES
618
619// Common interface of FreeRTOS mutex flavours. FrtosSemaphore::m_mtx always points to it and virtual calls
620// dispatch to the concrete type. NOTE: polymorphism is intentionally kept out of FrtosSemaphore itself, its
621// byte[0] must remain the SemKind discriminant (see GetSemKindFromHandle()).
623{
624 explicit FrtosMutexBase() {}
625 virtual ~FrtosMutexBase() {}
626
627 virtual bool Take(stk::Timeout timeout) = 0;
628 virtual void Give() = 0;
629 virtual stk::TId GetOwner() const = 0;
630 virtual bool IsRecursive() const = 0;
631};
632
633// Recursive mutex: xSemaphoreCreateRecursiveMutex[Static]()
635{
637
638 bool Take(stk::Timeout timeout) override { return m_mutex.TimedLock(timeout); }
639 void Give() override { m_mutex.Unlock(); }
640 stk::TId GetOwner() const override { return m_mutex.GetOwner(); }
641 bool IsRecursive() const override { return true; }
642
644};
645
646// Non-recursive mutex: xSemaphoreCreateMutex[Static]()
648{
650
651 bool Take(stk::Timeout timeout) override
652 {
653 // FreeRTOS: a non-recursive mutex taken again by its holder can never succeed (the call blocks until
654 // the timeout expires). sync::FastMutex would panic with KERNEL_PANIC_SYNC_DEADLOCK instead, so
655 // the self-take is rejected here without waiting. Only the holder itself can observe itself as the
656 // owner, so the check does not need a critical section.
657 const stk::TId owner = m_mutex.GetOwner();
658 if ((owner != stk::TID_NONE) && (owner == stk::GetTid()))
659 return false;
660
661 return m_mutex.TimedLock(timeout);
662 }
663
664 void Give() override { m_mutex.Unlock(); }
665 stk::TId GetOwner() const override { return m_mutex.GetOwner(); }
666 bool IsRecursive() const override { return false; }
667
669};
670
671#endif // configUSE_MUTEXES
672
674{
675 explicit FrtosSemaphore(SemKind kind, uint16_t initial, uint16_t max_count, bool recursive = false)
676 : m_kind(kind), m_cb_owned(true),
677 m_sem(nullptr)
679 , m_mtx(nullptr)
680#endif
682 , m_set(nullptr)
683 #endif
684 {
685 if (kind == SemKind::Counting)
686 m_sem = ObjAlloc<stk::sync::Semaphore>(initial, max_count);
687#if configUSE_MUTEXES
688 else if (recursive)
690 else
692#endif
693 }
694
696 {
698#if configUSE_MUTEXES
699 ObjFreeRaw(m_mtx); // virtual destructor of FrtosMutexBase destroys the concrete mutex
700#endif
701 }
702
703 // ---- Members ----
706 stk::sync::Semaphore *m_sem; // non-null for Counting kind
707#if configUSE_MUTEXES
708 FrtosMutexBase *m_mtx; // non-null for Mutex kind
709#endif
710#if configUSE_QUEUE_SETS
712#endif
713};
714
715// ===========================================================================
716// Queue Set control block
717//
718// A queue set is a supervising FIFO whose payload elements are void* handles
719// (sizeof(void*) bytes each). When a member queue or semaphore transitions
720// from empty to non-empty, the member's own handle is written into this FIFO
721// via QueueSetNotify(). xQueueSelectFromSet() then does a blocking Get()
722// of one handle from the FIFO, returning it to the caller.
723//
724// The internal MessageQueue stores pointer-sized tokens and is given a
725// capacity equal to uxEventQueueLength supplied by the application (which
726// per the FreeRTOS API contract must be >= the sum of all member capacities).
727//
728// Thread safety:
729// QueueSetNotify() is called from both task and ISR context immediately
730// after a successful Put/Signal on a member, before the critical section
731// opened by that Put/Signal is released. TryPut() (NO_WAIT) on the set
732// queue is ISR-safe per the STK MessageQueue contract, so no additional
733// locking is required here.
734//
735// Ownership model:
736// FrtosQueueSet is always heap-allocated (m_cb_owned = true).
737// Member pointers stored in m_mq are non-owning; the application owns all
738// member objects independently and must call xQueueRemoveFromSet() before
739// deleting any member or the set.
740// ===========================================================================
741
743{
744 // The payload of every slot in m_token_mq is exactly one void* — the
745 // handle of the member that fired. We use a raw byte array as the
746 // MessageQueue backing store to avoid a separate heap allocation.
747 explicit FrtosQueueSet(UBaseType_t uxEventQueueLength)
748 : m_buf(nullptr), m_cb_owned(true),
749 m_token_mq(nullptr)
750 {
751 // Allocate the flat byte ring-buffer: N slots × sizeof(void*) bytes.
752 const size_t buf_bytes =
753 static_cast<size_t>(uxEventQueueLength) * sizeof(void *);
754
755 m_buf = ObjAllocArray<uint8_t>(buf_bytes);
756 if (m_buf == nullptr)
757 return;
758
760 m_buf,
761 static_cast<size_t>(uxEventQueueLength),
762 sizeof(void *));
763 }
764
770
771 bool IsValid() const { return (m_token_mq != nullptr); }
772
773 // ---- Members ----
774 uint8_t *m_buf;
777};
778
779// -----------------------------------------------------------------------------
780// QueueSetNotify — called after every successful send/signal on a member.
781//
782// Posts the member's handle (a void*) into the set's token queue using a
783// non-blocking TryPut(). If the set queue is full the notification is
784// silently dropped, matching the FreeRTOS behaviour (which documents that
785// xEventQueueLength must be large enough to hold every possible concurrent
786// notification from all members without overflow).
787//
788// This function is ISR-safe: TryPut() uses NO_WAIT and a ScopedCriticalSection
789// internally, both of which are safe from interrupt context.
790//
791// Parameters:
792// member_handle — the void* handle of the queue or semaphore that fired.
793// set — the FrtosQueueSet that member belongs to.
794// -----------------------------------------------------------------------------
795template <typename THost>
796static inline void QueueSetNotify(void *member_handle, THost *host)
797{
798#if configUSE_QUEUE_SETS
799 // m_set is non-null only when the member was registered with xQueueAddToSet.
800 // Post the member handle as a pointer-sized token. TryPut is ISR-safe.
801 if (host->m_set != nullptr)
802 host->m_set->m_token_mq->TryPut(&member_handle);
803#else
804 STK_UNUSED(member_handle);
805 STK_UNUSED(host);
806#endif
807}
808
809// ===========================================================================
810// Software timer control block [configUSE_TIMERS]
811//
812// Backed by stk::time::TimerHost.
813// A single global TimerHost is created lazily on the first xTimerCreate().
814// ===========================================================================
815
816#if configUSE_TIMERS
817
819
820// Static storage for the TimerHost (avoids heap for the host object itself).
822
824{
825 explicit FrtosTimer(const char *name,
826 TickType_t period,
827 bool auto_reload,
828 void *timer_id,
830 : m_name(name), m_period(period), m_auto_reload(auto_reload),
831 m_timer_id(timer_id), m_callback(cb), m_cb_owned(true)
832 {}
833
834 virtual ~FrtosTimer() {}
835
836 void OnExpired(stk::time::TimerHost * /*host*/) override
837 {
838 m_callback(static_cast<TimerHandle_t>(this));
839 }
840
841 static bool EnsureTimerHost()
842 {
843 if (g_TimerHost == nullptr)
844 {
847 }
848 return (g_TimerHost != nullptr);
849 }
850
851 const char *m_name;
852 TickType_t m_period; // ticks, stored for Reset/ChangePeriod
857};
858
859// ===========================================================================
860// PendCall / g_PendCallPipe / FrtosPendDrainer
861//
862// Design
863// ------
864// A deferred call is represented as a plain value struct (PendCall) holding
865// the callback pointer and its two parameters. All in-flight calls live in a
866// statically-allocated ring-buffer:
867//
868// static stk::sync::PipeT<PendCall, FREERTOS_STK_PEND_CALL_QUEUE_SIZE>
869// g_PendCallPipe;
870//
871// FrtosPendDrainer is a singleton TimerHost::Timer that is started once (as a
872// 1-tick auto-reload timer) the first time xTimerPendFunctionCall[FromISR]()
873// is called. On every OnExpired() tick it drains g_PendCallPipe to completion,
874// invoking each PendCall callback in the TimerHost handler task context —
875// exactly where FreeRTOS's timer daemon would run them.
876//
877// Advantages
878// --------------------------------
879// * Zero heap allocation per call — no pvPortMalloc / ObjFreeRaw per pend.
880// * ISR path no longer requires the allocator to be ISR-reentrant.
881// * Static RAM cost is fixed and visible at link time
882// (FREERTOS_STK_PEND_CALL_QUEUE_SIZE * sizeof(PendCall) bytes).
883// * No self-deleting object pattern; ownership is unconditionally clear.
884// * PipeT::TryWrite() (used from ISR) is ISR-safe via ScopedCriticalSection.
885// * PipeT::Write() (used from task context) supports a real blocking timeout.
886//
887// Lifecycle of g_PendDrainer
888// --------------------------
889// Constructed in static storage (g_PendDrainerBuf) on first use.
890// Started as a 1-tick auto-reload timer so OnExpired() is called every tick
891// while the pipe is non-empty. To avoid burning timer ticks when idle the
892// drainer stops itself when it finds the pipe empty, and is re-started by
893// xTimerPendFunctionCall[FromISR]() whenever a new call is enqueued.
894// ===========================================================================
895
896// Value type: one deferred call record (no virtual dispatch, no heap).
898{
900 void *param1;
901 uint32_t param2;
902};
903
904// Static ring-buffer — capacity set by FREERTOS_STK_PEND_CALL_QUEUE_SIZE.
906
907// Static storage for the singleton drainer timer (avoids a heap allocation).
910
911// Singleton drainer: drains g_PendCallPipe each time it fires, then stops
912// itself when the pipe is empty to avoid unnecessary timer ticks.
914{
915 void OnExpired(stk::time::TimerHost *host) override
916 {
917 PendCall call;
918 while (g_PendCallPipe.TryRead(call))
919 call.func(call.param1, call.param2);
920
921 // Pipe is empty: stop the auto-reload drainer until the next enqueue.
922 // host->Stop() is safe to call from OnExpired() because the TimerHost
923 // removes the timer from the active list before dispatching OnExpired.
924 if (host != nullptr)
925 host->Stop(*this);
926 }
927};
928
929// Ensure the drainer timer is constructed and started (idempotent; ISR-unsafe).
930// Must be called from task context only (same restriction as EnsureTimerHost).
931static bool EnsurePendDrainer()
932{
933 if (g_PendDrainer == nullptr)
935
936 // Re-start as a 1-tick auto-reload timer each time we have new work.
937 // Restart() is idempotent if already active.
938 return g_TimerHost->Restart(*g_PendDrainer, 1U, 1U);
939}
940
941// Kick the drainer from ISR context after a TryWrite succeeds.
942// Uses Start() with NO_WAIT which is ISR-safe via PipeT / ScopedCriticalSection.
943// If the drainer is already active this is a no-op (Restart would re-arm it,
944// which is fine; the extra tick is harmless).
946{
947 if ((g_PendDrainer != nullptr) && (g_TimerHost != nullptr))
948 g_TimerHost->Restart(*g_PendDrainer, 1U, 1U);
949}
950
951#endif // configUSE_TIMERS
952
953// ===========================================================================
954// Event group control block [configUSE_EVENT_GROUPS]
955//
956// Backed by stk::sync::EventFlags (32-bit; bits 0..30 are usable,
957// bit 31 is reserved by STK for error sentinels).
958// FreeRTOS conventionally uses only bits 0..23 for event groups.
959// ===========================================================================
960
961#if configUSE_EVENT_GROUPS
962
964{
965 explicit FrtosEventGroup() : m_ef(0U), m_cb_owned(true)
966 {}
967
970};
971
972#endif // configUSE_EVENT_GROUPS
973
974// -----------------------------------------------------------------------------
975// FrtosStreamBuffer [configUSE_STREAM_BUFFERS]
976//
977// Backed by a stk::sync::Pipe with element_size = 1 (byte ring-buffer).
978// sync::Pipe is chosen over sync::MessageQueue because it exposes WriteBulk /
979// ReadBulk / TryWriteBulk / TryReadBulk directly, which are required for
980// efficient multi-byte stream transfers and for the trigger-level logic in
981// xStreamBufferReceive().
982//
983// The data buffer is either heap-owned (m_buf_owned = true) or external
984// (caller-supplied via xStreamBufferCreateStatic).
985//
986// Trigger level: xStreamBufferReceive() delegates entirely to
987// Pipe::ReadBulkTriggered(), which blocks until m_trigger bytes are present
988// and then drains up to xBufferLengthBytes in one atomic CS pass —
989// no busy-spin, no second call, no lost-wakeup risk.
990// -----------------------------------------------------------------------------
991
992#if configUSE_STREAM_BUFFERS
993
995{
996 // Constructor: caller pre-allocates buf and may transfer ownership via
997 // m_buf_owned / m_cb_owned (overridden by the Create helpers after construction).
998 // pSendCb / pRecvCb are optional per-instance notification callbacks;
999 // both default to nullptr (no callback).
1000 explicit FrtosStreamBuffer(uint8_t *buf,
1001 size_t capacity,
1002 size_t trigger,
1003 StreamBufferCallbackFunction_t pSendCb = nullptr,
1004 StreamBufferCallbackFunction_t pRecvCb = nullptr)
1005 : m_pipe(buf, capacity, 1U),
1006 m_trigger(trigger >= 1U ? trigger : 1U),
1007 m_buf_owned(false), // overridden to true by xStreamBufferCreate after ctor
1008 m_cb_owned(false), // overridden to true by xStreamBufferCreate after ctor
1009 m_send_cb(pSendCb),
1010 m_recv_cb(pRecvCb)
1011 {}
1012
1014 {
1015 if (m_buf_owned)
1016 ObjFreeArray(m_pipe.GetBuffer());
1017 }
1018
1019 // ---- Members ----
1021 size_t m_trigger;
1026};
1027
1028// -----------------------------------------------------------------------------
1029// FrtosMessageBuffer
1030//
1031// An envelope struct is pushed into m_eq for every message. The payload lives
1032// in a block pool block. On Receive() the envelope is popped, payload copied
1033// out, and the block returned to the pool.
1034//
1035// Layout of the caller-supplied flat buffer for the static constructor:
1036// [ block_pool_storage | envelope_queue_storage ]
1037// where
1038// block_pool_storage = xMessageCount * AlignBlockSize(xMaxMessageSize)
1039// envelope_queue_storage = xMessageCount * sizeof(MsgEnvelope)
1040// -----------------------------------------------------------------------------
1042{
1044 {
1045 size_t len;
1046 void *blk;
1047 };
1048
1049 static constexpr size_t ENVELOPE_SIZE = sizeof(MsgEnvelope);
1050
1051 // Heap constructor.
1052 // pSendCb / pRecvCb are optional per-instance notification callbacks;
1053 // both default to nullptr (no callback).
1054 explicit FrtosMessageBuffer(size_t max_msg_size,
1055 size_t msg_count,
1056 StreamBufferCallbackFunction_t pSendCb = nullptr,
1057 StreamBufferCallbackFunction_t pRecvCb = nullptr)
1058 : m_pool(msg_count,
1059 stk::memory::BlockMemoryPool::AlignBlockSize(max_msg_size)),
1060 m_eq(ObjAllocArray<uint8_t>(msg_count * ENVELOPE_SIZE),
1061 msg_count, ENVELOPE_SIZE),
1062 m_max_msg_size(max_msg_size),
1063 m_eq_buf_owned(true),
1064 m_cb_owned(true),
1065 m_send_cb(pSendCb),
1066 m_recv_cb(pRecvCb)
1067 {}
1068
1069 // Static constructor: uses caller-supplied flat storage buffer.
1070 // Layout: [pool_storage | eq_storage] as described above.
1071 explicit FrtosMessageBuffer(size_t max_msg_size,
1072 size_t msg_count,
1073 uint8_t *storage,
1074 size_t storage_size,
1075 StreamBufferCallbackFunction_t pSendCb = nullptr,
1076 StreamBufferCallbackFunction_t pRecvCb = nullptr)
1077 : m_pool(msg_count,
1078 stk::memory::BlockMemoryPool::AlignBlockSize(max_msg_size),
1079 storage,
1080 msg_count * stk::memory::BlockMemoryPool::AlignBlockSize(max_msg_size)),
1081 m_eq(storage + msg_count * stk::memory::BlockMemoryPool::AlignBlockSize(max_msg_size),
1082 msg_count, ENVELOPE_SIZE),
1083 m_max_msg_size(max_msg_size),
1084 m_eq_buf_owned(false),
1085 m_cb_owned(false),
1086 m_send_cb(pSendCb),
1087 m_recv_cb(pRecvCb)
1088 {
1089 STK_UNUSED(storage_size);
1090 }
1091
1093 {
1094 if (m_eq_buf_owned)
1095 ObjFreeArray(static_cast<uint8_t *>(m_eq.GetBuffer()));
1096 }
1097
1098 // ---- Members ----
1106};
1107
1108#endif // configUSE_STREAM_BUFFERS
1109
1110// Ensure kernel is initialized.
1112{
1114 {
1115 g_StkKernel.Initialize(); // default 1 ms tick resolution
1116 }
1117}
1118
1119// ===========================================================================
1120// Kernel control
1121// ===========================================================================
1122
1124{
1126
1127 g_StkKernel.Start(); // does not return for KERNEL_DYNAMIC until all tasks exit
1128}
1129
1131{
1132 g_StkKernel.EnumerateTasksT<FREERTOS_STK_MAX_TASKS>([&](stk::ITask *task) -> bool
1133 {
1134 g_StkKernel.ScheduleTaskRemoval(task);
1135 return true;
1136 });
1137
1138 stk::Yield();
1139}
1140
1145
1147{
1149 return pdFALSE; // no pending switch tracked at wrapper level
1150}
1151
1153{
1154 return static_cast<TickType_t>(stk::GetTicks());
1155}
1156
1158{
1159 return static_cast<TickType_t>(stk::GetTicks()); // GetTicks() is ISR-safe
1160}
1161
1163{
1165 return static_cast<UBaseType_t>(g_StkKernel.GetSwitchStrategy()->GetSize());
1166}
1167
1169{
1170 // Map the four STK kernel states onto the three FreeRTOS scheduler states:
1171 // STATE_INACTIVE / STATE_READY -> NOT_STARTED (scheduler never ran)
1172 // STATE_RUNNING -> RUNNING
1173 // STATE_SUSPENDED -> SUSPENDED
1174 switch (g_StkKernel.GetState())
1175 {
1178 default: return taskSCHEDULER_NOT_STARTED;
1179 }
1180}
1181
1182// ===========================================================================
1183// Task management
1184// ===========================================================================
1185
1187 const char *pcName,
1188 uint32_t usStackDepth,
1189 void *pvParameters,
1190 UBaseType_t uxPriority,
1191 TaskHandle_t *pxCreatedTask)
1192{
1193 if (pvTaskCode == nullptr)
1194 return pdFAIL;
1195
1197 if (t == nullptr)
1198 return pdFAIL;
1199
1200 t->m_func = pvTaskCode;
1201 t->m_argument = pvParameters;
1202 t->m_name = pcName;
1203 t->m_weight = FrtosPrioToStkWeight(uxPriority);
1204
1205 // Determine stack size in Words.
1206 size_t stack_words = (usStackDepth > 0U)
1207 ? static_cast<size_t>(usStackDepth)
1209
1210 if (stack_words < FREERTOS_STK_MIN_STACK_WORDS)
1211 stack_words = FREERTOS_STK_MIN_STACK_WORDS;
1212
1213 t->m_stack = ObjAllocArray<stk::Word>(stack_words);
1214 if (t->m_stack == nullptr)
1215 {
1216 ObjFree(t);
1217 return pdFAIL;
1218 }
1219
1220 t->m_stack_size = stack_words;
1221 t->m_stack_owned = true;
1222
1224
1225 g_StkKernel.AddTask(t);
1226
1227 if (pxCreatedTask != nullptr)
1228 *pxCreatedTask = static_cast<TaskHandle_t>(t);
1229
1230 return pdPASS;
1231}
1232
1234 const char *pcName,
1235 uint32_t ulStackDepth,
1236 void *pvParameters,
1237 UBaseType_t uxPriority,
1238 StackType_t *puxStackBuffer,
1239 StaticTask_t *pxTaskBuffer)
1240{
1241 // All three pointer arguments are mandatory for static allocation.
1242 if ((pvTaskCode == nullptr) || (puxStackBuffer == nullptr) || (pxTaskBuffer == nullptr))
1243 return nullptr;
1244
1245 // Placement-new the FrtosTask control block into the caller-supplied TCB
1246 // buffer. Static assert guards against the buffer being too small.
1247 static_assert(sizeof(StaticTask_t) >= sizeof(FrtosTask),
1248 "StaticTask_t is too small to hold FrtosTask. "
1249 "Increase STATIC_TASK_TCB_SIZE_WORDS in freertos_stk.h.");
1250
1251 FrtosTask *t = new (pxTaskBuffer) FrtosTask();
1252
1253 t->m_func = pvTaskCode;
1254 t->m_argument = pvParameters;
1255 t->m_name = pcName;
1256 t->m_weight = FrtosPrioToStkWeight(uxPriority);
1257 t->m_stack = static_cast<stk::Word *>(static_cast<void *>(puxStackBuffer));
1258 t->m_stack_size = (ulStackDepth >= FREERTOS_STK_MIN_STACK_WORDS)
1259 ? static_cast<size_t>(ulStackDepth)
1261 t->m_stack_owned = false; // caller owns both the stack and the TCB
1262 t->m_cb_owned = false; // destructor must not delete — caller owns memory
1263
1265
1266 g_StkKernel.AddTask(t);
1267
1268 return static_cast<TaskHandle_t>(t);
1269}
1270
1271void vTaskDelete(TaskHandle_t xTaskToDelete)
1272{
1273 FrtosTask *t = (xTaskToDelete == nullptr) ? static_cast<FrtosTask *>(
1274 reinterpret_cast<FrtosTask *>(static_cast<uintptr_t>(stk::GetTid()))) :
1275 static_cast<FrtosTask *>(xTaskToDelete);
1276
1277 if (t == nullptr)
1278 return;
1279
1281
1282 g_StkKernel.ScheduleTaskRemoval(t);
1283
1284 // Detached tasks are freed when the slot is released.
1285 // For this wrapper, all tasks are considered detached (no join semantics).
1286 ObjFree(t);
1287}
1288
1289void vTaskSuspend(TaskHandle_t xTaskToSuspend)
1290{
1291 FrtosTask *t = (xTaskToSuspend == nullptr) ? static_cast<FrtosTask *>(
1292 reinterpret_cast<FrtosTask *>(static_cast<uintptr_t>(stk::GetTid()))) :
1293 static_cast<FrtosTask *>(xTaskToSuspend);
1294
1295 if (t == nullptr)
1296 return;
1297
1298 bool already = false;
1299 g_StkKernel.SuspendTask(t, already);
1301}
1302
1303void vTaskResume(TaskHandle_t xTaskToResume)
1304{
1305 if (xTaskToResume == nullptr)
1306 return;
1307
1308 FrtosTask *t = static_cast<FrtosTask *>(xTaskToResume);
1309
1311
1313 return;
1314
1315 g_StkKernel.ResumeTask(t);
1317}
1318
1320{
1321 if (xTaskToResume == nullptr)
1322 return pdFALSE;
1323
1324 FrtosTask *t = static_cast<FrtosTask *>(xTaskToResume);
1325
1327
1329 return pdFALSE;
1330
1331 g_StkKernel.ResumeTask(t);
1333
1334 return pdTRUE;
1335}
1336
1338{
1339 if (xTask == nullptr)
1340 return pdFALSE;
1341
1342 // Resolve NULL -> calling task (same convention used throughout the wrapper).
1343 const stk::TId tid = static_cast<stk::TId>(reinterpret_cast<uintptr_t>(xTask));
1344
1345 const FrtosTask *t = static_cast<const FrtosTask *>(xTask);
1347 return pdFAIL; // suspended or otherwise not in a delay-able state
1348
1349 // FreeRTOS: only a task in the Blocked state can have its delay aborted. The calling task is
1350 // running by definition, so it can never be blocked (and SleepCancel() would be a no-op for it).
1351 if (tid == stk::GetTid())
1352 return pdFAIL;
1353
1354 // Ends a vTaskDelay()/vTaskDelayUntil() sleep, and also a blocking wait on a sync object
1355 // (xQueueReceive/xQueueSend, xSemaphoreTake, xEventGroupWaitBits, xTaskNotifyWait, ...): the
1356 // wait ends with WAIT_RESULT_CANCELED, which the wrapper reports exactly like a timeout
1357 // (pdFAIL / nullptr / 0 / flags snapshot), as FreeRTOS does for an aborted delay.
1358 stk::SleepCancel(tid);
1359 return pdPASS;
1360}
1361
1362void vTaskDelay(TickType_t xTicksToDelay)
1363{
1364 if (IsIrqContext())
1365 return;
1366
1367 stk::Sleep(FrtosTimeoutToStk(xTicksToDelay));
1368}
1369
1370void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement)
1371{
1372 static_cast<void>(xTaskDelayUntil(pxPreviousWakeTime, xTimeIncrement));
1373}
1374
1375BaseType_t xTaskDelayUntil(TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement)
1376{
1377 if (IsIrqContext() || (pxPreviousWakeTime == nullptr))
1378 return pdFALSE;
1379
1380 const stk::Ticks wake_at = static_cast<stk::Ticks>(*pxPreviousWakeTime) +
1381 static_cast<stk::Ticks>(xTimeIncrement);
1382
1383 *pxPreviousWakeTime = static_cast<TickType_t>(wake_at);
1384
1385 return stk::SleepUntil(wake_at) ? pdTRUE : pdFALSE;
1386}
1387
1389{
1390 FrtosTask *t = (xTask == nullptr) ? reinterpret_cast<FrtosTask *>(
1391 static_cast<uintptr_t>(stk::GetTid())) : static_cast<FrtosTask *>(xTask);
1392
1393 if (t == nullptr)
1394 return;
1395
1396 t->m_weight = FrtosPrioToStkWeight(uxNewPriority);
1397}
1398
1400{
1401 const FrtosTask *t = (xTask == nullptr) ? reinterpret_cast<const FrtosTask *>(
1402 static_cast<uintptr_t>(stk::GetTid())) : static_cast<const FrtosTask *>(xTask);
1403
1404 if (t == nullptr)
1405 return 0U;
1406
1407 return StkWeightToFrtosPrio(t->m_weight);
1408}
1409
1411{
1412 return uxTaskPriorityGet(xTask); // same implementation; GetTid() is ISR-safe
1413}
1414
1416{
1417 if (xTask == nullptr)
1418 return eInvalid;
1419
1420 FrtosTask *t = static_cast<FrtosTask *>(xTask);
1421
1423 return eDeleted;
1424
1426 return eSuspended;
1427
1428 // Check whether this is the currently running task.
1429 if (static_cast<uintptr_t>(stk::GetTid()) == reinterpret_cast<uintptr_t>(t))
1430 return eRunning;
1431
1432 return eReady;
1433}
1434
1436{
1437 if (IsIrqContext())
1438 return nullptr;
1439
1440 return reinterpret_cast<TaskHandle_t>(static_cast<uintptr_t>(stk::GetTid()));
1441}
1442
1443TaskHandle_t xTaskGetHandle(const char *pcNameToQuery)
1444{
1445 if (pcNameToQuery == nullptr)
1446 return nullptr;
1447
1448 // Enumerate all tasks and compare names.
1449 TaskHandle_t found = nullptr;
1450
1451 g_StkKernel.EnumerateTasksT<FREERTOS_STK_MAX_TASKS>([&](stk::ITask *task) -> bool
1452 {
1453 if ((task->GetTraceName() != nullptr) &&
1454 (FreertosStrcmp(task->GetTraceName(), pcNameToQuery) == 0))
1455 {
1456 found = static_cast<TaskHandle_t>(task);
1457 return false; // stop iteration
1458 }
1459 return true;
1460 });
1461
1462 return found;
1463}
1464
1465const char *pcTaskGetName(TaskHandle_t xTaskToQuery)
1466{
1467 if (xTaskToQuery == nullptr)
1468 return nullptr;
1469
1470 return static_cast<FrtosTask *>(xTaskToQuery)->m_name;
1471}
1472
1474{
1475 const FrtosTask *t = (xTask == nullptr) ? reinterpret_cast<const FrtosTask *>(
1476 static_cast<uintptr_t>(stk::GetTid())) : static_cast<const FrtosTask *>(xTask);
1477
1478 if (t == nullptr)
1479 return 0U;
1480
1481 return static_cast<UBaseType_t>(t->GetStackHighWaterMark());
1482}
1483
1485{
1486 const FrtosTask *t = (xTask == nullptr) ? reinterpret_cast<const FrtosTask *>(
1487 static_cast<uintptr_t>(stk::GetTid())) : static_cast<const FrtosTask *>(xTask);
1488
1489 if (t == nullptr)
1490 return 0U;
1491
1492 return static_cast<configSTACK_DEPTH_TYPE>(t->GetStackHighWaterMark());
1493}
1494
1496 UBaseType_t uxArraySize,
1497 uint32_t *pulTotalRunTime)
1498{
1499 // STK has no global CPU run-time accumulator; report 0 per the FreeRTOS
1500 // convention for targets that do not implement run-time statistics.
1501 if (pulTotalRunTime != nullptr)
1502 *pulTotalRunTime = 0U;
1503
1504 if ((pxTaskStatusArray == nullptr) || (uxArraySize == 0U))
1505 return 0U;
1506
1507 UBaseType_t filled = 0U;
1508
1509 // Identify the currently running task once, outside the enumeration
1510 // loop, so the eRunning check is consistent across all entries.
1511 const uintptr_t running_tid = static_cast<uintptr_t>(stk::GetTid());
1512
1513 g_StkKernel.EnumerateTasksT<FREERTOS_STK_MAX_TASKS>([&](stk::ITask *itask) -> bool
1514 {
1515 if (filled >= uxArraySize)
1516 return false; // array full — stop enumeration
1517
1518 const FrtosTask *t = static_cast<const FrtosTask *>(itask);
1519 TaskStatus_t &s = pxTaskStatusArray[filled];
1520
1521 // xHandle — the FrtosTask pointer cast to an opaque handle.
1522 s.xHandle = static_cast<TaskHandle_t>(const_cast<FrtosTask *>(t));
1523
1524 // pcTaskName — direct pointer into the task's name buffer (not a copy).
1525 s.pcTaskName = (t->m_name != nullptr) ? t->m_name : "";
1526
1527 // eCurrentState — mirrors eTaskGetState() logic.
1530 else
1533 else
1534 if (reinterpret_cast<uintptr_t>(t) == running_tid)
1536 else
1538
1539 // uxCurrentPriority / uxBasePriority — same value: STK has no
1540 // priority inheritance so the current and base priority are identical.
1543
1544 // ulRunTimeCounter — always 0 (STK has no per-task CPU accounting).
1545 s.ulRunTimeCounter = 0U;
1546
1547 // pxStackBase — bottom of the stack array (index 0).
1548 s.pxStackBase = reinterpret_cast<StackType_t *>(t->m_stack);
1549
1550 // usStackHighWaterMark — minimum observed free Words (watermark scan).
1553
1554 // xTaskNumber — monotonic serial assigned at construction.
1556
1557 ++filled;
1558 return true; // continue enumeration
1559 });
1560
1561 return filled;
1562}
1563
1565 TaskHandle_t *pxCreatedTask)
1566{
1567 // Mandatory pointer guard.
1568 if ((pxTaskDefinition == nullptr) ||
1569 (pxTaskDefinition->pvTaskCode == nullptr) ||
1570 (pxTaskDefinition->puxStackBuffer == nullptr) ||
1571 (pxTaskDefinition->pxTaskBuffer == nullptr))
1572 return pdFAIL;
1573
1574 // STK does not implement MPU support. Forward to xTaskCreateStatic(),
1575 // accepting but ignoring the xRegions MPU region table.
1576 // TODO: program pxTaskDefinition->xRegions into the MPU when STK gains
1577 // hardware MPU support.
1579 pxTaskDefinition->pvTaskCode,
1580 pxTaskDefinition->pcName,
1581 pxTaskDefinition->usStackDepth,
1582 pxTaskDefinition->pvParameters,
1583 pxTaskDefinition->uxPriority,
1584 pxTaskDefinition->puxStackBuffer,
1585 pxTaskDefinition->pxTaskBuffer);
1586
1587 if (h == nullptr)
1588 return pdFAIL;
1589
1590 if (pxCreatedTask != nullptr)
1591 *pxCreatedTask = h;
1592
1593 return pdPASS;
1594}
1595
1597 TaskHandle_t *pxCreatedTask)
1598{
1599 // Mandatory pointer guard (only pvTaskCode is required; the caller need not
1600 // supply puxStackBuffer or pxTaskBuffer — both are heap-allocated here).
1601 if ((pxTaskDefinition == nullptr) ||
1602 (pxTaskDefinition->pvTaskCode == nullptr))
1603 return pdFAIL;
1604
1605 // STK does not implement MPU support. Forward to xTaskCreate() which
1606 // heap-allocates both the TCB and the task stack, accepting but ignoring
1607 // the xRegions MPU region table.
1608 // TODO: program pxTaskDefinition->xRegions into the MPU when STK gains
1609 // hardware MPU support.
1610 return xTaskCreate(
1611 pxTaskDefinition->pvTaskCode,
1612 pxTaskDefinition->pcName,
1613 pxTaskDefinition->usStackDepth,
1614 pxTaskDefinition->pvParameters,
1615 pxTaskDefinition->uxPriority,
1616 pxCreatedTask);
1617}
1618
1619void vTaskList(char *pcWriteBuffer)
1620{
1621 if (pcWriteBuffer == nullptr)
1622 return;
1623
1624 // Write the column header that FreeRTOS vTaskList() produces, so that
1625 // existing log parsers find what they expect.
1626 int off = snprintf(pcWriteBuffer, 64U,
1627 "%-12s %c %4s %6s %4s\r\n",
1628 "Name", 'S', "Prio", "Stack", "Num");
1629
1630 if (off < 0) off = 0;
1631 char *p = pcWriteBuffer + off;
1632
1633 // Enumerate all tasks and fill one row per task.
1634 UBaseType_t task_num = 0U;
1635
1636 g_StkKernel.EnumerateTasksT<FREERTOS_STK_MAX_TASKS>([&](stk::ITask *itask) -> bool
1637 {
1638 ++task_num;
1639 FrtosTask *t = static_cast<FrtosTask *>(itask);
1640
1641 // Determine state letter, matching FreeRTOS convention:
1642 // X = Running, R = Ready, B = Blocked, S = Suspended, D = Deleted
1643 char state_letter;
1645 state_letter = 'D';
1646 else
1648 state_letter = 'S';
1649 else
1650 if (static_cast<uintptr_t>(stk::GetTid()) == reinterpret_cast<uintptr_t>(t))
1651 state_letter = 'X';
1652 else
1653 state_letter = 'R';
1654
1655 const char *name = (t->m_name != nullptr) ? t->m_name : "(unnamed)";
1657 size_t hwm = t->GetStackHighWaterMark();
1658
1659 int n = snprintf(p, 48U, "%-12s %c %4u %6u %4u\r\n",
1660 name, state_letter,
1661 static_cast<unsigned>(prio),
1662 static_cast<unsigned>(hwm),
1663 static_cast<unsigned>(task_num));
1664 if (n > 0) p += n;
1665
1666 return true; // continue enumeration
1667 });
1668
1669 *p = '\0'; // null-terminate
1670}
1671
1672// -----------------------------------------------------------------------------
1673// vTaskGetRunTimeStats
1674// -----------------------------------------------------------------------------
1675// Produces the standard FreeRTOS run-time statistics table:
1676//
1677// Task Abs Time % Time
1678// ----------------------------------------
1679// TaskName 0 0%
1680// ...
1681//
1682// STK has no per-task CPU run-time accumulator (ulRunTimeCounter is always 0
1683// and the total run time reported by uxTaskGetSystemState() is always 0).
1684// Following the FreeRTOS convention for targets where
1685// configGENERATE_RUN_TIME_STATS is disabled, both columns are emitted as 0.
1686// The function exists for link compatibility with middleware and diagnostic
1687// tools that call it unconditionally.
1688// -----------------------------------------------------------------------------
1689
1690void vTaskGetRunTimeStats(char *pcWriteBuffer)
1691{
1692 if (pcWriteBuffer == nullptr)
1693 return;
1694
1695 // Column header matching FreeRTOS vTaskGetRunTimeStats() output format so
1696 // that existing log parsers (SystemView, Tracealyzer, custom scripts) find
1697 // the layout they expect.
1698 int off = snprintf(pcWriteBuffer, 64U,
1699 "%-12s %12s %8s\r\n",
1700 "Task", "Abs Time", "% Time");
1701
1702 if (off < 0) off = 0;
1703 char *p = pcWriteBuffer + off;
1704
1705 g_StkKernel.EnumerateTasksT<FREERTOS_STK_MAX_TASKS>([&](stk::ITask *itask) -> bool
1706 {
1707 const FrtosTask *t = static_cast<const FrtosTask *>(itask);
1708 const char *name = (t->m_name != nullptr) ? t->m_name : "(unnamed)";
1709
1710 // ulRunTimeCounter is always 0: STK has no per-task CPU accounting.
1711 // Percentage is therefore also 0. Emit "<1%" only when a non-zero
1712 // total is available; here total is always 0 so we emit "0%".
1713 int n = snprintf(p, 48U, "%-12s %12lu %7lu%%\r\n",
1714 name,
1715 0UL, // ulRunTimeCounter
1716 0UL); // percentage
1717 if (n > 0) p += n;
1718
1719 return true; // continue enumeration
1720 });
1721
1722 *p = '\0'; // null-terminate
1723}
1724
1725// ===========================================================================
1726// Queue API
1727// ===========================================================================
1728
1730 UBaseType_t uxItemSize)
1731{
1732 if (IsIrqContext() || (uxQueueLength == 0U) || (uxItemSize == 0U))
1733 return nullptr;
1734
1735 if (uxQueueLength > stk::sync::MessageQueue::CAPACITY_MAX)
1736 return nullptr;
1737
1739 static_cast<uint32_t>(uxQueueLength),
1740 static_cast<uint32_t>(uxItemSize),
1741 nullptr /* name */);
1742
1743 if (q == nullptr)
1744 return nullptr;
1745
1746 if (!q->m_mq.IsStorageValid())
1747 {
1748 ObjFree(q);
1749 return nullptr;
1750 }
1751
1752 return static_cast<QueueHandle_t>(q);
1753}
1754
1756 UBaseType_t uxItemSize,
1757 uint8_t *pucQueueStorage,
1758 StaticQueue_t *pxStaticQueue)
1759{
1760 // All pointer arguments are mandatory for static allocation.
1761 if ((pucQueueStorage == nullptr) || (pxStaticQueue == nullptr))
1762 return nullptr;
1763
1764 if (IsIrqContext() || (uxQueueLength == 0U) || (uxItemSize == 0U))
1765 return nullptr;
1766
1767 if (uxQueueLength > stk::sync::MessageQueue::CAPACITY_MAX)
1768 return nullptr;
1769
1770 // Placement-new the FrtosQueue control block into the caller-supplied buffer.
1771 // Static assert guards against the buffer being too small.
1772 static_assert(sizeof(StaticQueue_t) >= sizeof(FrtosQueue),
1773 "StaticQueue_t is too small to hold FrtosQueue. "
1774 "Increase STATIC_QUEUE_TCB_SIZE_WORDS in freertos_stk.h.");
1775
1776 // Use the external-storage FrtosQueue constructor: no heap allocation for
1777 // either the control block or the data buffer.
1778 FrtosQueue *q = new (pxStaticQueue) FrtosQueue(
1779 static_cast<uint32_t>(uxQueueLength),
1780 static_cast<uint32_t>(uxItemSize),
1781 nullptr /* name */,
1782 pucQueueStorage);
1783
1784 q->m_cb_owned = false; // caller owns the memory; destructor must not delete
1785
1786 return static_cast<QueueHandle_t>(q);
1787}
1788
1790{
1791 if (xQueue == nullptr)
1792 return;
1793
1794 ObjFree(static_cast<FrtosQueue *>(xQueue));
1795}
1796
1798 const void *pvItemToQueue,
1799 TickType_t xTicksToWait)
1800{
1801 if ((xQueue == nullptr) || (pvItemToQueue == nullptr))
1802 return pdFAIL;
1803
1804 if (IsIrqContext() && (xTicksToWait != 0U))
1805 return pdFAIL;
1806
1807 FrtosQueue *q = static_cast<FrtosQueue *>(xQueue);
1808
1809 if (!q->m_mq.Put(pvItemToQueue, FrtosTimeoutToStk(xTicksToWait)))
1810 return pdFAIL;
1811
1812 QueueSetNotify(xQueue, q);
1813 return pdPASS;
1814}
1815
1817 const void *pvItemToQueue,
1818 TickType_t xTicksToWait)
1819{
1820 return xQueueSend(xQueue, pvItemToQueue, xTicksToWait);
1821}
1822
1824 const void *pvItemToQueue,
1825 TickType_t xTicksToWait)
1826{
1827 if ((xQueue == nullptr) || (pvItemToQueue == nullptr))
1828 return pdFAIL;
1829
1830 if (IsIrqContext() && (xTicksToWait != 0U))
1831 return pdFAIL;
1832
1833 FrtosQueue *q = static_cast<FrtosQueue *>(xQueue);
1834
1835 if (!q->m_mq.PutFront(pvItemToQueue, FrtosTimeoutToStk(xTicksToWait)))
1836 return pdFAIL;
1837
1838 QueueSetNotify(xQueue, q);
1839 return pdPASS;
1840}
1841
1843 void *pvBuffer,
1844 TickType_t xTicksToWait)
1845{
1846 if ((xQueue == nullptr) || (pvBuffer == nullptr))
1847 return pdFAIL;
1848
1849 if (IsIrqContext() && (xTicksToWait != 0U))
1850 return pdFAIL;
1851
1852 return static_cast<FrtosQueue *>(xQueue)->m_mq.Get(pvBuffer, FrtosTimeoutToStk(xTicksToWait))
1853 ? pdPASS : pdFAIL;
1854}
1855
1857 void *pvBuffer,
1858 TickType_t xTicksToWait)
1859{
1860 // Delegates to Peek(), which copies the oldest message without consuming
1861 // it. The operation is fully atomic and preserves queue ordering — no
1862 // Get + Put-back workaround required.
1863 if ((xQueue == nullptr) || (pvBuffer == nullptr))
1864 return pdFAIL;
1865
1866 if (IsIrqContext() && (xTicksToWait != 0U))
1867 return pdFAIL;
1868
1869 return static_cast<FrtosQueue *>(xQueue)->m_mq.Peek(
1870 pvBuffer, FrtosTimeoutToStk(xTicksToWait))
1871 ? pdPASS : pdFAIL;
1872}
1873
1875 void *pvBuffer)
1876{
1877 // Delegates to TryPeek() (= Peek(NO_WAIT)), which is ISR-safe and copies
1878 // the oldest message atomically without removing it.
1879 if ((xQueue == nullptr) || (pvBuffer == nullptr))
1880 return pdFAIL;
1881
1882 return static_cast<FrtosQueue *>(xQueue)->m_mq.TryPeek(pvBuffer) ? pdPASS : pdFAIL;
1883}
1884
1886{
1887 if (xQueue == nullptr)
1888 return 0U;
1889
1890 return static_cast<UBaseType_t>(
1891 static_cast<FrtosQueue *>(xQueue)->m_mq.GetCount());
1892}
1893
1895{
1896 // GetCount() is ISR-safe on targets where a size_t-aligned read is atomic
1897 // (per the STK MessageQueue documentation).
1898 if (xQueue == nullptr)
1899 return 0U;
1900
1901 return static_cast<UBaseType_t>(static_cast<FrtosQueue *>(xQueue)->m_mq.GetCount());
1902}
1903
1905{
1906 if (xQueue == nullptr)
1907 return 0U;
1908
1909 return static_cast<UBaseType_t>(
1910 static_cast<FrtosQueue *>(xQueue)->m_mq.GetSpace());
1911}
1912
1914{
1915 if (xQueue == nullptr)
1916 return pdFAIL;
1917
1918 static_cast<FrtosQueue *>(xQueue)->m_mq.Reset();
1919 return pdPASS;
1920}
1921
1922BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void *pvItemToQueue)
1923{
1924 // Mailbox (length-1 queue) overwrite pattern.
1925 // Reset() atomically discards any existing item and wakes blocked producers,
1926 // guaranteeing TryPut() will always find a free slot immediately after.
1927 if ((xQueue == nullptr) || (pvItemToQueue == nullptr))
1928 return pdFAIL;
1929
1930 FrtosQueue *q = static_cast<FrtosQueue *>(xQueue);
1931 q->m_mq.Reset();
1932 q->m_mq.TryPut(pvItemToQueue);
1933 QueueSetNotify(xQueue, q);
1934
1935 return pdPASS;
1936}
1937
1939 const void *pvItemToQueue,
1940 BaseType_t *pxHigherPriorityTaskWoken)
1941{
1942 // ISR-safe variant of xQueueOverwrite. Reset() and TryPut() are both
1943 // ISR-safe per the STK MessageQueue contract.
1944 if ((xQueue == nullptr) || (pvItemToQueue == nullptr))
1945 return pdFAIL;
1946
1947 FrtosQueue *q = static_cast<FrtosQueue *>(xQueue);
1948 q->m_mq.Reset();
1949 q->m_mq.TryPut(pvItemToQueue);
1950 QueueSetNotify(xQueue, q);
1951
1952 if (pxHigherPriorityTaskWoken != nullptr)
1953 *pxHigherPriorityTaskWoken = pdFALSE;
1954
1955 return pdPASS;
1956}
1957
1959 const void *pvItemToQueue,
1960 BaseType_t *pxHigherPriorityTaskWoken)
1961{
1962 if ((xQueue == nullptr) || (pvItemToQueue == nullptr))
1963 return pdFAIL;
1964
1965 FrtosQueue *q = static_cast<FrtosQueue *>(xQueue);
1966 bool ok = q->m_mq.TryPut(pvItemToQueue);
1967
1968 if (ok)
1969 QueueSetNotify(xQueue, q);
1970
1971 // STK handles the wake-up internally; the wrapper does not need to
1972 // request an explicit yield from ISR because SWRR re-evaluates on the
1973 // next tick. Set the flag to pdFALSE to avoid spurious portYIELD_FROM_ISR.
1974 if (pxHigherPriorityTaskWoken != nullptr)
1975 *pxHigherPriorityTaskWoken = pdFALSE;
1976
1977 return ok ? pdPASS : pdFAIL;
1978}
1979
1981 void *pvBuffer,
1982 BaseType_t *pxHigherPriorityTaskWoken)
1983{
1984 if ((xQueue == nullptr) || (pvBuffer == nullptr))
1985 return pdFAIL;
1986
1987 bool ok = static_cast<FrtosQueue *>(xQueue)->m_mq.TryGet(pvBuffer);
1988
1989 if (pxHigherPriorityTaskWoken != nullptr)
1990 *pxHigherPriorityTaskWoken = pdFALSE;
1991
1992 return ok ? pdPASS : pdFAIL;
1993}
1994
1996 const void *pvItemToQueue,
1997 BaseType_t *pxHigherPriorityTaskWoken)
1998{
1999 // Send-to-back from ISR is identical to xQueueSendFromISR: TryPut()
2000 // appends to the back of the ring buffer.
2001 return xQueueSendFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken);
2002}
2003
2005 const void *pvItemToQueue,
2006 BaseType_t *pxHigherPriorityTaskWoken)
2007{
2008 if ((xQueue == nullptr) || (pvItemToQueue == nullptr))
2009 return pdFAIL;
2010
2011 FrtosQueue *q = static_cast<FrtosQueue *>(xQueue);
2012 bool ok = q->m_mq.TryPutFront(pvItemToQueue);
2013
2014 if (ok)
2015 QueueSetNotify(xQueue, q);
2016
2017 // STK handles the wake-up internally; set the flag to pdFALSE to avoid
2018 // spurious portYIELD_FROM_ISR.
2019 if (pxHigherPriorityTaskWoken != nullptr)
2020 *pxHigherPriorityTaskWoken = pdFALSE;
2021
2022 return ok ? pdPASS : pdFAIL;
2023}
2024
2026{
2027 // IsEmpty() reads m_count which is size_t-aligned; ISR-safe on targets
2028 // where such a read is atomic (per stk::sync::MessageQueue contract).
2029 if (xQueue == nullptr)
2030 return pdTRUE;
2031
2032 return static_cast<const FrtosQueue *>(xQueue)->m_mq.IsEmpty() ? pdTRUE : pdFALSE;
2033}
2034
2036{
2037 // IsFull() reads m_count and m_capacity; both are size_t-aligned and
2038 // m_capacity is const, so the read is ISR-safe on naturally-atomic targets
2039 // (per stk::sync::MessageQueue contract).
2040 if (xQueue == nullptr)
2041 return pdTRUE;
2042
2043 return static_cast<const FrtosQueue *>(xQueue)->m_mq.IsFull() ? pdTRUE : pdFALSE;
2044}
2045
2046// -----------------------------------------------------------------------------
2047// xQueueGetMutexHolder / xQueueGetMutexHolderFromISR
2048// -----------------------------------------------------------------------------
2049// FreeRTOS re-uses its internal queue struct for mutex semaphores, so these
2050// two functions exist as QueueHandle_t-typed aliases of the semaphore
2051// counterparts. In this STK wrapper the object types are distinct:
2052//
2053// FrtosQueue — wraps stk::sync::MessageQueue; no mutex, no owner.
2054// FrtosSemaphore — wraps stk::sync::Semaphore (Counting) or
2055// stk::sync::FastMutex / stk::sync::Mutex (Mutex kind).
2056//
2057// Type discrimination:
2058// The first byte at the handle address is read via GetSemKindFromHandle(),
2059// which returns SemKind::None for any byte that is not a known SemKind
2060// enumerator (0x80 = Counting, 0x81 = Mutex). FrtosQueue byte[0] is
2061// byte[0] of stk::sync::MessageQueue (an ITraceable vtable/data pointer)
2062// and is never 0x80 or 0x81 under normal linking, so GetSemKindFromHandle()
2063// reliably returns SemKind::None for plain queue handles.
2064//
2065// GetSemKindFromHandle() == SemKind::Mutex -> FrtosSemaphore (Mutex kind)
2066// GetSemKindFromHandle() != SemKind::Mutex -> FrtosQueue or other (no owner)
2067//
2068// If the handle is a FrtosSemaphore with SemKind::Mutex the call is forwarded
2069// to xSemaphoreGetMutexHolder[FromISR]() which reads GetOwner() of the mutex.
2070// For a plain FrtosQueue, or for a counting/binary semaphore, NULL is returned
2071// because the owner concept does not apply to those object types.
2072//
2073// STK mutexes (FastMutex and Mutex) always support priority inheritance; no additional bookkeeping is
2074// required here — GetOwner() already reflects the current holder.
2075// -----------------------------------------------------------------------------
2076
2077#if configUSE_MUTEXES
2078
2079// Helper: given a raw QueueHandle_t, return the FrtosSemaphore* if the handle
2080// is actually a mutex-kind semaphore, or nullptr otherwise.
2082{
2083 if (xQueue == nullptr)
2084 return nullptr;
2085
2086 // GetSemKindFromHandle() returns SemKind::None for FrtosQueue handles
2087 // (and any unrecognised object), SemKind::Counting or SemKind::Mutex for
2088 // FrtosSemaphore handles. Only SemKind::Mutex carries an owner field.
2089 if (GetSemKindFromHandle(xQueue) != SemKind::Mutex)
2090 return nullptr;
2091
2092 return static_cast<FrtosSemaphore *>(xQueue);
2093}
2094
2096{
2097 // Resolve to FrtosSemaphore (Mutex kind) or bail out.
2099 if (s == nullptr)
2100 return nullptr;
2101
2102 // Delegate to the semaphore variant which acquires a ScopedCriticalSection
2103 // to make the TId snapshot consistent with concurrent Unlock() calls.
2104 return xSemaphoreGetMutexHolder(static_cast<SemaphoreHandle_t>(xQueue));
2105}
2106
2108{
2109 // Resolve to FrtosSemaphore (Mutex kind) or bail out.
2111 if (s == nullptr)
2112 return nullptr;
2113
2114 // Delegate to the ISR-safe semaphore variant which reads GetOwner() via a
2115 // single pointer-sized atomic load — no additional critical section needed.
2116 return xSemaphoreGetMutexHolderFromISR(static_cast<SemaphoreHandle_t>(xQueue));
2117}
2118
2119#endif // configUSE_MUTEXES
2120
2121// ===========================================================================
2122// Queue Set API
2123//
2124// A queue set acts as a fan-in multiplexer: one task can block on multiple
2125// queues and/or binary/counting semaphores simultaneously, waking as soon as
2126// any member receives an item.
2127//
2128// Implementation model:
2129// FrtosQueueSet owns an internal stk::sync::MessageQueue whose payload
2130// element size is sizeof(void*). Whenever a member queue or semaphore
2131// successfully receives a new item it writes its own handle (a void*)
2132// into this FIFO via QueueSetNotify(). xQueueSelectFromSet() performs a
2133// blocking Get() from the same FIFO and returns the handle to the caller.
2134//
2135// FreeRTOS API contracts enforced here:
2136// - Members must not already belong to another set (asserted).
2137// - Members must be empty when removed from a set (asserted).
2138// - Mutexes must not be added to queue sets (FreeRTOS API restriction).
2139// - xEventQueueLength must be >= sum of all member capacities; the caller
2140// is responsible for sizing correctly. Overflow is silently dropped by
2141// TryPut() in QueueSetNotify(), matching the FreeRTOS reference behaviour.
2142//
2143// Restrictions (matching FreeRTOS):
2144// - xQueueOverwrite / xQueueOverwriteFromISR should not be used with queues
2145// that are members of a set, as the overwrite generates a set notification
2146// even when the old value is replaced rather than a new slot filled, which
2147// can produce spurious wakeups. This mirrors the documented FreeRTOS
2148// caveat.
2149// ===========================================================================
2150#if configUSE_QUEUE_SETS
2151
2153{
2154 if (IsIrqContext() || (uxEventQueueLength == 0U))
2155 return nullptr;
2156
2157 if (uxEventQueueLength > stk::sync::MessageQueue::CAPACITY_MAX)
2158 return nullptr;
2159
2160 FrtosQueueSet *qs = ObjAlloc<FrtosQueueSet>(uxEventQueueLength);
2161
2162 if ((qs == nullptr) || !qs->IsValid())
2163 {
2164 ObjFree(qs);
2165 return nullptr;
2166 }
2167
2168 return static_cast<QueueSetHandle_t>(qs);
2169}
2170
2172 QueueSetHandle_t xQueueSet)
2173{
2174 if ((xQueueOrSemaphore == nullptr) || (xQueueSet == nullptr))
2175 return pdFAIL;
2176
2177 FrtosQueueSet *qs = static_cast<FrtosQueueSet *>(xQueueSet);
2178
2179 // Type discrimination between FrtosQueue and FrtosSemaphore via
2180 // GetSemKindFromHandle(): returns SemKind::None for queues (and any
2181 // unrecognised handle), SemKind::Counting or SemKind::Mutex for semaphores.
2182 {
2183 const SemKind kind = GetSemKindFromHandle(xQueueOrSemaphore);
2184
2185 if (kind != SemKind::None)
2186 {
2187 // Semaphore or mutex path.
2188 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xQueueOrSemaphore);
2189
2190 // FreeRTOS API contract: mutexes cannot be queue set members.
2191 if (s->m_kind == SemKind::Mutex)
2192 return pdFAIL;
2193
2194 STK_ASSERT(s->m_set == nullptr); // must not already belong to a set
2195 if (s->m_set != nullptr)
2196 return pdFAIL;
2197
2198 STK_ASSERT(s->m_sem->GetCount() == 0U); // must be empty when added
2199 if (s->m_sem->GetCount() != 0U)
2200 return pdFAIL;
2201
2202 s->m_set = qs;
2203 }
2204 else
2205 {
2206 // Queue path.
2207 FrtosQueue *q = static_cast<FrtosQueue *>(xQueueOrSemaphore);
2208
2209 STK_ASSERT(q->m_set == nullptr); // must not already belong to a set
2210 if (q->m_set != nullptr)
2211 return pdFAIL;
2212
2213 STK_ASSERT(q->m_mq.IsEmpty()); // must be empty when added
2214 if (!q->m_mq.IsEmpty())
2215 return pdFAIL;
2216
2217 q->m_set = qs;
2218 }
2219 }
2220
2221 return pdPASS;
2222}
2223
2225 QueueSetHandle_t xQueueSet)
2226{
2227 if ((xQueueOrSemaphore == nullptr) || (xQueueSet == nullptr))
2228 return pdFAIL;
2229
2230 {
2231 const SemKind kind = GetSemKindFromHandle(xQueueOrSemaphore);
2232
2233 if (kind != SemKind::None)
2234 {
2235 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xQueueOrSemaphore);
2236
2237 // API contract: member must belong to this specific set.
2238 STK_ASSERT(s->m_set == static_cast<FrtosQueueSet *>(xQueueSet));
2239 if (s->m_set != static_cast<FrtosQueueSet *>(xQueueSet))
2240 return pdFAIL;
2241
2242 // API contract: semaphore must be empty when removed.
2243 STK_ASSERT(s->m_sem->GetCount() == 0U);
2244 if (s->m_sem->GetCount() != 0U)
2245 return pdFAIL;
2246
2247 s->m_set = nullptr;
2248 }
2249 else
2250 {
2251 FrtosQueue *q = static_cast<FrtosQueue *>(xQueueOrSemaphore);
2252
2253 STK_ASSERT(q->m_set == static_cast<FrtosQueueSet *>(xQueueSet));
2254 if (q->m_set != static_cast<FrtosQueueSet *>(xQueueSet))
2255 return pdFAIL;
2256
2257 // API contract: queue must be empty when removed.
2258 STK_ASSERT(q->m_mq.IsEmpty());
2259 if (!q->m_mq.IsEmpty())
2260 return pdFAIL;
2261
2262 q->m_set = nullptr;
2263 }
2264 }
2265
2266 return pdPASS;
2267}
2268
2270 TickType_t xTicksToWait)
2271{
2272 if (xQueueSet == nullptr)
2273 return nullptr;
2274
2275 if (IsIrqContext() && (xTicksToWait != 0U))
2276 return nullptr;
2277
2278 FrtosQueueSet *qs = static_cast<FrtosQueueSet *>(xQueueSet);
2279
2280 // Block until a member handle arrives in the token FIFO or timeout fires.
2281 void *handle = nullptr;
2282 if (!qs->m_token_mq->Get(&handle, FrtosTimeoutToStk(xTicksToWait)))
2283 return nullptr; // timeout or aborted wait (xTaskAbortDelay)
2284
2285 return static_cast<QueueSetMemberHandle_t>(handle);
2286}
2287
2289{
2290 if (xQueueSet == nullptr)
2291 return nullptr;
2292
2293 FrtosQueueSet *qs = static_cast<FrtosQueueSet *>(xQueueSet);
2294
2295 // Non-blocking: TryGet() is ISR-safe per the STK MessageQueue contract.
2296 void *handle = nullptr;
2297 if (!qs->m_token_mq->TryGet(&handle))
2298 return nullptr; // set is empty
2299
2300 return static_cast<QueueSetMemberHandle_t>(handle);
2301}
2302
2303#endif // configUSE_QUEUE_SETS
2304
2305// ===========================================================================
2306// Semaphore / Mutex API
2307// ===========================================================================
2308
2310{
2311 // Binary semaphore: max count = 1, initial count = 0.
2314 static_cast<uint16_t>(0U),
2315 static_cast<uint16_t>(1U));
2316
2317 if ((s == nullptr) || (s->m_sem == nullptr))
2318 {
2319 ObjFree(s);
2320 return nullptr;
2321 }
2322 return static_cast<SemaphoreHandle_t>(s);
2323}
2324
2326{
2327 if (pxSemaphoreBuffer == nullptr)
2328 return nullptr;
2329
2330 // Static assert guards against the buffer being too small.
2331 static_assert(sizeof(StaticSemaphore_t) >= sizeof(FrtosSemaphore),
2332 "StaticSemaphore_t is too small to hold FrtosSemaphore. "
2333 "Increase STATIC_SEMAPHORE_TCB_SIZE_WORDS in freertos_stk.h.");
2334
2335 // Placement-new the FrtosSemaphore control block into the caller-supplied
2336 // buffer. Binary semaphore: max count = 1, initial count = 0.
2337 // The inner stk::sync::Semaphore is a value type embedded inside
2338 // FrtosSemaphore, so no additional heap allocation is needed.
2339 FrtosSemaphore *s = new (pxSemaphoreBuffer) FrtosSemaphore(
2341 static_cast<uint16_t>(0U),
2342 static_cast<uint16_t>(1U));
2343
2344 if (s->m_sem == nullptr)
2345 {
2346 s->~FrtosSemaphore(); // clean up without freeing
2347 return nullptr;
2348 }
2349
2350 s->m_cb_owned = false; // caller owns the memory; destructor must not delete
2351
2352 return static_cast<SemaphoreHandle_t>(s);
2353}
2354
2355#if configUSE_COUNTING_SEMAPHORES
2356
2358 UBaseType_t uxInitialCount)
2359{
2360 if (uxMaxCount == 0U || uxInitialCount > uxMaxCount)
2361 return nullptr;
2362
2363 if (uxMaxCount > stk::sync::Semaphore::COUNT_MAX)
2365
2368 static_cast<uint16_t>(uxInitialCount),
2369 static_cast<uint16_t>(uxMaxCount));
2370
2371 if ((s == nullptr) || (s->m_sem == nullptr))
2372 {
2373 ObjFree(s);
2374 return nullptr;
2375 }
2376 return static_cast<SemaphoreHandle_t>(s);
2377}
2378
2380 UBaseType_t uxInitialCount,
2381 StaticSemaphore_t *pxSemaphoreBuffer)
2382{
2383 if (pxSemaphoreBuffer == nullptr)
2384 return nullptr;
2385
2386 if (uxMaxCount == 0U || uxInitialCount > uxMaxCount)
2387 return nullptr;
2388
2389 if (uxMaxCount > stk::sync::Semaphore::COUNT_MAX)
2391
2392 static_assert(sizeof(StaticSemaphore_t) >= sizeof(FrtosSemaphore),
2393 "StaticSemaphore_t is too small to hold FrtosSemaphore. "
2394 "Increase STATIC_SEMAPHORE_TCB_SIZE_WORDS in freertos_stk.h.");
2395
2396 FrtosSemaphore *s = new (pxSemaphoreBuffer) FrtosSemaphore(
2398 static_cast<uint16_t>(uxInitialCount),
2399 static_cast<uint16_t>(uxMaxCount));
2400
2401 if (s->m_sem == nullptr)
2402 {
2403 s->~FrtosSemaphore();
2404 return nullptr;
2405 }
2406
2407 s->m_cb_owned = false;
2408 return static_cast<SemaphoreHandle_t>(s);
2409}
2410
2411#endif // configUSE_COUNTING_SEMAPHORES
2412
2413#if configUSE_MUTEXES
2414
2415// Create a heap-allocated mutex semaphore; recursive selects stk::sync::Mutex, otherwise stk::sync::FastMutex.
2417{
2420 static_cast<uint16_t>(0U),
2421 static_cast<uint16_t>(1U),
2422 recursive);
2423
2424 if ((s == nullptr) || (s->m_mtx == nullptr))
2425 {
2426 ObjFree(s);
2427 return nullptr;
2428 }
2429
2430 return static_cast<SemaphoreHandle_t>(s);
2431}
2432
2433// Create a mutex semaphore with the control block placed in the caller-supplied buffer.
2435{
2436 if (pxMutexBuffer == nullptr)
2437 return nullptr;
2438
2439 static_assert(sizeof(StaticSemaphore_t) >= sizeof(FrtosSemaphore),
2440 "StaticSemaphore_t is too small to hold FrtosSemaphore. "
2441 "Increase STATIC_SEMAPHORE_TCB_SIZE_WORDS in freertos_stk.h.");
2442
2443 FrtosSemaphore *s = new (pxMutexBuffer) FrtosSemaphore(
2445 static_cast<uint16_t>(0U),
2446 static_cast<uint16_t>(1U),
2447 recursive);
2448
2449 if (s->m_mtx == nullptr)
2450 {
2451 s->~FrtosSemaphore();
2452 return nullptr;
2453 }
2454
2455 s->m_cb_owned = false;
2456 return static_cast<SemaphoreHandle_t>(s);
2457}
2458
2460{
2461 // Non-recursive: backed by stk::sync::FastMutex.
2462 return CreateMutexInternal(false);
2463}
2464
2466{
2467 // Non-recursive: backed by stk::sync::FastMutex.
2468 return CreateMutexStaticInternal(pxMutexBuffer, false);
2469}
2470
2472{
2473 // Recursive: backed by stk::sync::Mutex.
2474 return CreateMutexInternal(true);
2475}
2476
2478{
2479 // Recursive: backed by stk::sync::Mutex.
2480 return CreateMutexStaticInternal(pxMutexBuffer, true);
2481}
2482
2483#endif // configUSE_MUTEXES
2484
2486{
2487 if (xSemaphore == nullptr)
2488 return;
2489
2490 ObjFree(static_cast<FrtosSemaphore *>(xSemaphore));
2491}
2492
2494{
2495 if (xSemaphore == nullptr)
2496 return pdFAIL;
2497
2498 if (IsIrqContext() && (xTicksToWait != 0U))
2499 return pdFAIL;
2500
2501 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xSemaphore);
2502 stk::Timeout tmo = FrtosTimeoutToStk(xTicksToWait);
2503
2504 if (s->m_kind == SemKind::Mutex)
2505 {
2506#if configUSE_MUTEXES
2507 return s->m_mtx->Take(tmo) ? pdPASS : pdFAIL;
2508#else
2509 return pdFAIL;
2510#endif
2511 }
2512 else
2513 return s->m_sem->Wait(tmo) ? pdPASS : pdFAIL;
2514}
2515
2517 BaseType_t *pxHigherPriorityTaskWoken)
2518{
2519 if (xSemaphore == nullptr)
2520 return pdFAIL;
2521
2522 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xSemaphore);
2523
2524 // Mutex take from ISR is not permitted — mirrors xSemaphoreGiveFromISR.
2525 if (s->m_kind == SemKind::Mutex)
2526 return pdFAIL;
2527
2528 // TryWait() is Wait(NO_WAIT): decrement count if > 0, return immediately.
2529 // ISR-safe per the STK Semaphore contract.
2530 bool ok = s->m_sem->TryWait();
2531
2532 // STK handles priority re-evaluation internally on the next tick.
2533 // Set pdFALSE to avoid spurious portYIELD_FROM_ISR, matching the
2534 // pattern used by xQueueReceiveFromISR and xSemaphoreGiveFromISR.
2535 if (pxHigherPriorityTaskWoken != nullptr)
2536 *pxHigherPriorityTaskWoken = pdFALSE;
2537
2538 return ok ? pdPASS : pdFAIL;
2539}
2540
2542{
2543 if (xMutex == nullptr)
2544 return pdFAIL;
2545
2546#if configUSE_MUTEXES
2547 const FrtosSemaphore *s = static_cast<const FrtosSemaphore *>(xMutex);
2548
2549 // FreeRTOS API contract: only handles created by xSemaphoreCreateRecursiveMutex[Static]() are allowed.
2550 if ((s->m_kind != SemKind::Mutex) || !s->m_mtx->IsRecursive())
2551 return pdFAIL;
2552
2553 return xSemaphoreTake(xMutex, xTicksToWait);
2554#else
2555 return pdFAIL;
2556#endif
2557}
2558
2560{
2561 if (xSemaphore == nullptr)
2562 return pdFAIL;
2563
2564 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xSemaphore);
2565
2566 if (s->m_kind == SemKind::Mutex)
2567 {
2568#if configUSE_MUTEXES
2569 // FreeRTOS: only the holder can give a mutex back (otherwise pdFAIL). The check is enforced in all
2570 // build configurations, the kernel itself only asserts the ownership in debug builds.
2571 const stk::TId owner = s->m_mtx->GetOwner();
2572 if ((owner == stk::TID_NONE) || (owner != stk::GetTid()))
2573 return pdFAIL;
2574
2575 s->m_mtx->Give();
2576 // Mutexes are not eligible for queue sets (FreeRTOS API contract),
2577 // so no QueueSetNotify call is needed here.
2578 return pdPASS;
2579#else
2580 return pdFAIL;
2581#endif
2582 }
2583 else
2584 {
2585 // Guard against overflow: TryWait + Signal pattern.
2587 return pdFAIL;
2588
2589 s->m_sem->Signal();
2590 QueueSetNotify(xSemaphore, s);
2591 return pdPASS;
2592 }
2593}
2594
2596{
2597 if (xMutex == nullptr)
2598 return pdFAIL;
2599
2600#if configUSE_MUTEXES
2601 const FrtosSemaphore *s = static_cast<const FrtosSemaphore *>(xMutex);
2602
2603 // FreeRTOS API contract: only handles created by xSemaphoreCreateRecursiveMutex[Static]() are allowed.
2604 if ((s->m_kind != SemKind::Mutex) || !s->m_mtx->IsRecursive())
2605 return pdFAIL;
2606
2607 return xSemaphoreGive(xMutex);
2608#else
2609 return pdFAIL;
2610#endif
2611}
2612
2614 BaseType_t *pxHigherPriorityTaskWoken)
2615{
2616 if (xSemaphore == nullptr)
2617 return pdFAIL;
2618
2619 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xSemaphore);
2620
2621 if (s->m_kind == SemKind::Mutex)
2622 return pdFAIL; // Mutex give from ISR is not permitted.
2623
2625 return pdFAIL;
2626
2627 s->m_sem->Signal();
2628 QueueSetNotify(xSemaphore, s);
2629
2630 if (pxHigherPriorityTaskWoken != nullptr)
2631 *pxHigherPriorityTaskWoken = pdFALSE;
2632
2633 return pdPASS;
2634}
2635
2637{
2638 if (xSemaphore == nullptr)
2639 return 0U;
2640
2641 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xSemaphore);
2642
2643 if (s->m_kind == SemKind::Counting)
2644 return static_cast<UBaseType_t>(s->m_sem->GetCount());
2645
2646#if configUSE_MUTEXES
2647 // For a mutex, count = 0 means locked, 1 means unlocked.
2648 return (s->m_mtx->GetOwner() == stk::TID_NONE) ? 1U : 0U;
2649#else
2650 return 0U;
2651#endif
2652}
2653
2654#if configUSE_MUTEXES
2655
2657{
2658 // Returns the task that currently owns the mutex, or NULL if the mutex
2659 // is unlocked or xMutex is not a mutex-kind semaphore.
2660 //
2661 // FreeRTOS documents this function as not ISR-safe and requiring the
2662 // scheduler to be running. We guard it with a ScopedCriticalSection
2663 // so that the TId snapshot is consistent: if Unlock() is executing
2664 // concurrently, we see either the old owner or TID_NONE, never a torn
2665 // pointer.
2666 //
2667 // TId -> TaskHandle_t: STK stores task pointers as TId values via
2668 // TId = static_cast<TId>(reinterpret_cast<uintptr_t>(task_ptr))
2669 // so the inverse is:
2670 // task_ptr = reinterpret_cast<FrtosTask *>(static_cast<uintptr_t>(tid))
2671 // TID_NONE maps to nullptr (TaskHandle_t == nullptr means "no owner").
2672 if (xMutex == nullptr)
2673 return nullptr;
2674
2675 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xMutex);
2676
2677 if (s->m_kind != SemKind::Mutex)
2678 return nullptr; // not a mutex — owner concept does not apply
2679
2681
2682 const stk::TId owner = s->m_mtx->GetOwner();
2683
2684 if (owner == stk::TID_NONE)
2685 return nullptr; // mutex is currently unlocked
2686
2687 return reinterpret_cast<TaskHandle_t>(static_cast<uintptr_t>(owner));
2688}
2689
2691{
2692 // ISR-safe variant. GetOwner() reads a single TId (pointer-sized, aligned)
2693 // which is an atomic read on all supported STK architectures, so no
2694 // ScopedCriticalSection is needed here beyond what the caller already holds.
2695 // We still validate the handle and the semaphore kind before touching the
2696 // mutex state.
2697 if (xMutex == nullptr)
2698 return nullptr;
2699
2700 FrtosSemaphore *s = static_cast<FrtosSemaphore *>(xMutex);
2701
2702 if (s->m_kind != SemKind::Mutex)
2703 return nullptr;
2704
2705 const stk::TId owner = s->m_mtx->GetOwner();
2706
2707 if (owner == stk::TID_NONE)
2708 return nullptr;
2709
2710 return reinterpret_cast<TaskHandle_t>(static_cast<uintptr_t>(owner));
2711}
2712
2713#endif // configUSE_MUTEXES
2714
2715// ===========================================================================
2716// Software Timer API [configUSE_TIMERS]
2717// ===========================================================================
2718
2719#if configUSE_TIMERS
2720
2721TimerHandle_t xTimerCreate(const char *pcTimerName,
2722 TickType_t xTimerPeriodInTicks,
2723 UBaseType_t uxAutoReload,
2724 void *pvTimerID,
2725 TimerCallbackFunction_t pxCallbackFunction)
2726{
2727 if (IsIrqContext() || (pxCallbackFunction == nullptr) || (xTimerPeriodInTicks == 0U))
2728 return nullptr;
2729
2731 return nullptr;
2732
2734 pcTimerName,
2735 xTimerPeriodInTicks,
2736 (uxAutoReload == pdTRUE),
2737 pvTimerID,
2738 pxCallbackFunction);
2739
2740 return static_cast<TimerHandle_t>(t);
2741}
2742
2743TimerHandle_t xTimerCreateStatic(const char *pcTimerName,
2744 TickType_t xTimerPeriodInTicks,
2745 UBaseType_t uxAutoReload,
2746 void *pvTimerID,
2747 TimerCallbackFunction_t pxCallbackFunction,
2748 StaticTimer_t *pxTimerBuffer)
2749{
2750 if (pxTimerBuffer == nullptr)
2751 return nullptr;
2752
2753 if (IsIrqContext() || (pxCallbackFunction == nullptr) || (xTimerPeriodInTicks == 0U))
2754 return nullptr;
2755
2757 return nullptr;
2758
2759 static_assert(sizeof(StaticTimer_t) >= sizeof(FrtosTimer),
2760 "StaticTimer_t is too small to hold FrtosTimer. "
2761 "Increase STATIC_TIMER_TCB_SIZE_WORDS in freertos_stk.h.");
2762
2763 // Placement-new the FrtosTimer into the caller-supplied buffer.
2764 FrtosTimer *t = new (pxTimerBuffer) FrtosTimer(
2765 pcTimerName,
2766 xTimerPeriodInTicks,
2767 (uxAutoReload == pdTRUE),
2768 pvTimerID,
2769 pxCallbackFunction);
2770
2771 t->m_cb_owned = false; // caller owns the memory; xTimerDelete must not delete it
2772
2773 return static_cast<TimerHandle_t>(t);
2774}
2775
2777{
2778 if (xTimer == nullptr)
2779 return pdFAIL;
2780
2781 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2782
2783 if ((g_TimerHost != nullptr) && t->IsActive())
2784 g_TimerHost->Stop(*t);
2785
2786 ObjFree(t);
2787 return pdPASS;
2788}
2789
2791{
2792 if (IsIrqContext() || (xTimer == nullptr) || (g_TimerHost == nullptr))
2793 return pdFAIL;
2794
2795 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2796
2797 uint32_t period = t->m_auto_reload
2798 ? static_cast<uint32_t>(t->m_period)
2799 : 0U; // 0 = one-shot (no reload period)
2800
2801 return g_TimerHost->Restart(*t, static_cast<uint32_t>(t->m_period), period)
2802 ? pdPASS : pdFAIL;
2803}
2804
2806{
2807 if (IsIrqContext() || (xTimer == nullptr) || (g_TimerHost == nullptr))
2808 return pdFAIL;
2809
2810 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2811
2812 if (!t->IsActive())
2813 return pdFAIL;
2814
2815 return g_TimerHost->Stop(*t) ? pdPASS : pdFAIL;
2816}
2817
2819{
2820 return xTimerStart(xTimer, xTicksToWait); // Restart restarts from now
2821}
2822
2824 TickType_t xNewPeriod,
2825 TickType_t xTicksToWait)
2826{
2827 if ((xTimer == nullptr) || (xNewPeriod == 0U))
2828 return pdFAIL;
2829
2830 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2831 t->m_period = xNewPeriod;
2832
2833 return xTimerStart(xTimer, xTicksToWait);
2834}
2835
2836// ===========================================================================
2837// Timer ISR API
2838//
2839// TimerHost::PushCommand() calls m_commands.Write(cmd, NO_WAIT), which maps to
2840// PipeT<TimerCommand, N>::Write(..., NO_WAIT). With NO_WAIT, Write() acquires
2841// a ScopedCriticalSection (interrupt-safe on all STK targets), checks for
2842// space, copies the command if available, and returns immediately without
2843// sleeping — making it unconditionally safe to call from an ISR.
2844//
2845// xTicksToWait is accepted for FreeRTOS API compatibility but always ignored.
2846// pxHigherPriorityTaskWoken is always set to pdFALSE: the tick task wakes
2847// itself when it drains the command queue, no manual yield is required.
2848//
2849// Each ISR function is a thin wrapper that:
2850// 1. Sets *pxHigherPriorityTaskWoken = pdFALSE.
2851// 2. Validates arguments (null handle, null host, zero period).
2852// 3. Delegates to the matching task-context function, which calls PushCommand.
2853// The IsIrqContext() guard in xTimerStart/Stop is bypassed here because
2854// ISR variants call PushCommand directly through g_TimerHost.
2855// ===========================================================================
2856
2858 BaseType_t *pxHigherPriorityTaskWoken)
2859{
2860 if (pxHigherPriorityTaskWoken != nullptr)
2861 *pxHigherPriorityTaskWoken = pdFALSE;
2862
2863 if ((xTimer == nullptr) || (g_TimerHost == nullptr))
2864 return pdFAIL;
2865
2866 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2867 uint32_t period = t->m_auto_reload ? static_cast<uint32_t>(t->m_period) : 0U;
2868
2869 return g_TimerHost->Restart(*t, static_cast<uint32_t>(t->m_period), period)
2870 ? pdPASS : pdFAIL;
2871}
2872
2874 BaseType_t *pxHigherPriorityTaskWoken)
2875{
2876 if (pxHigherPriorityTaskWoken != nullptr)
2877 *pxHigherPriorityTaskWoken = pdFALSE;
2878
2879 if ((xTimer == nullptr) || (g_TimerHost == nullptr))
2880 return pdFAIL;
2881
2882 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2883
2884 if (!t->IsActive())
2885 return pdFAIL;
2886
2887 return g_TimerHost->Stop(*t) ? pdPASS : pdFAIL;
2888}
2889
2891 BaseType_t *pxHigherPriorityTaskWoken)
2892{
2893 // Reset = Restart from now, same as xTimerStart.
2894 return xTimerStartFromISR(xTimer, pxHigherPriorityTaskWoken);
2895}
2896
2898 TickType_t xNewPeriod,
2899 BaseType_t *pxHigherPriorityTaskWoken)
2900{
2901 if (pxHigherPriorityTaskWoken != nullptr)
2902 *pxHigherPriorityTaskWoken = pdFALSE;
2903
2904 if ((xTimer == nullptr) || (xNewPeriod == 0U) || (g_TimerHost == nullptr))
2905 return pdFAIL;
2906
2907 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
2908 t->m_period = xNewPeriod;
2909
2910 // Restart with the new period, taking effect immediately.
2911 uint32_t period = t->m_auto_reload ? static_cast<uint32_t>(xNewPeriod) : 0U;
2912
2913 return g_TimerHost->Restart(*t, static_cast<uint32_t>(xNewPeriod), period) ? pdPASS : pdFAIL;
2914}
2915
2916// ===========================================================================
2917// xTimerPendFunctionCall / xTimerPendFunctionCallFromISR
2918//
2919// Implementation strategy
2920// -----------------------
2921// Both functions write a PendCall value into g_PendCallPipe — a statically-
2922// allocated stk::sync::PipeT<PendCall, FREERTOS_STK_PEND_CALL_QUEUE_SIZE>.
2923// No heap allocation is performed.
2924//
2925// The singleton FrtosPendDrainer timer (stored in g_PendDrainerBuf) is
2926// (re-)started as a 1-tick auto-reload timer after every successful enqueue.
2927// On each tick it drains g_PendCallPipe to completion inside the TimerHost
2928// handler task — exactly where FreeRTOS's timer daemon would invoke the
2929// callbacks — then stops itself when the pipe is empty.
2930//
2931// Task-context variant (xTimerPendFunctionCall)
2932// ---------------------------------------------
2933// Uses PipeT::Write() with the caller-supplied xTicksToWait timeout, so
2934// the call can block if the pipe is momentarily full. Returns pdFAIL only
2935// if the timeout expires before a free slot becomes available.
2936//
2937// ISR-context variant (xTimerPendFunctionCallFromISR)
2938// ---------------------------------------------------
2939// Uses PipeT::TryWrite() (= Write with NO_WAIT), which acquires a
2940// ScopedCriticalSection internally and is unconditionally ISR-safe.
2941// No allocator call is made; pvPortMalloc ISR-reentrancy is not required.
2942// Returns pdFAIL immediately if the pipe is full.
2943//
2944// TimerHost initialization
2945// ------------------------
2946// EnsureTimerHost() is called from the task-context variant for the same
2947// reason as in xTimerCreate(): the API must work even if the application
2948// has not created any explicit timers. From ISR, the host must already be
2949// running (g_TimerHost == nullptr -> pdFAIL), matching FreeRTOS behaviour.
2950// ===========================================================================
2951
2953 void *pvParameter1,
2954 uint32_t ulParameter2,
2955 TickType_t xTicksToWait)
2956{
2957 // API contract: must not be called from ISR context.
2958 if (IsIrqContext())
2959 return pdFAIL;
2960
2961 if (xFunctionToPend == nullptr)
2962 return pdFAIL;
2963
2964 // Ensure the TimerHost (and hence the drainer's scheduling context) exists.
2966 return pdFAIL;
2967
2968 // Write the call record into the static pipe (blocking with timeout).
2969 // PipeT::Write() acquires a ScopedCriticalSection internally.
2970 const PendCall call = { xFunctionToPend, pvParameter1, ulParameter2 };
2971 if (!g_PendCallPipe.Write(call, FrtosTimeoutToStk(xTicksToWait)))
2972 return pdFAIL;
2973
2974 // (Re-)start the singleton drainer so it wakes within 1 tick.
2975 // EnsurePendDrainer() constructs the drainer on first call and
2976 // calls TimerHost::Restart() which is task-safe.
2978
2979 return pdPASS;
2980}
2981
2983 void *pvParameter1,
2984 uint32_t ulParameter2,
2985 BaseType_t *pxHigherPriorityTaskWoken)
2986{
2987 // STK wakes the tick task internally; no manual context switch needed.
2988 if (pxHigherPriorityTaskWoken != nullptr)
2989 *pxHigherPriorityTaskWoken = pdFALSE;
2990
2991 if (xFunctionToPend == nullptr)
2992 return pdFAIL;
2993
2994 // From ISR the TimerHost must already be running (a timer was created
2995 // before the ISR fired — the only realistic usage pattern).
2996 if (g_TimerHost == nullptr)
2997 return pdFAIL;
2998
2999 // Non-blocking enqueue: TryWrite() holds a ScopedCriticalSection internally
3000 // — unconditionally ISR-safe, no allocator call, no blocking.
3001 const PendCall call = { xFunctionToPend, pvParameter1, ulParameter2 };
3002 if (!g_PendCallPipe.TryWrite(call))
3003 return pdFAIL;
3004
3005 // Kick the drainer via ISR-safe TimerHost::Restart().
3007
3008 return pdPASS;
3009}
3010
3012{
3013 if (xTimer == nullptr)
3014 return pdFALSE;
3015
3016 return static_cast<FrtosTimer *>(xTimer)->IsActive() ? pdTRUE : pdFALSE;
3017}
3018
3020{
3021 if (xTimer == nullptr)
3022 return nullptr;
3023
3024 return static_cast<FrtosTimer *>(xTimer)->m_timer_id;
3025}
3026
3027void vTimerSetTimerID(TimerHandle_t xTimer, void *pvNewID)
3028{
3029 if (xTimer == nullptr)
3030 return;
3031
3032 static_cast<FrtosTimer *>(xTimer)->m_timer_id = pvNewID;
3033}
3034
3036{
3037 if (xTimer == nullptr)
3038 return nullptr;
3039
3040 return static_cast<FrtosTimer *>(xTimer)->m_name;
3041}
3042
3044{
3045 if (xTimer == nullptr)
3046 return 0U;
3047
3048 return static_cast<FrtosTimer *>(xTimer)->m_period;
3049}
3050
3052{
3053 if ((xTimer == nullptr) || (g_TimerHost == nullptr))
3054 return 0U;
3055
3056 FrtosTimer *t = static_cast<FrtosTimer *>(xTimer);
3057 return t->IsActive() ? static_cast<TickType_t>(t->GetDeadline()) : 0U;
3058}
3059
3060#endif // configUSE_TIMERS
3061
3062// ===========================================================================
3063// Event Group API [configUSE_EVENT_GROUPS]
3064// ===========================================================================
3065
3066#if configUSE_EVENT_GROUPS
3067
3068// Translate STK EventFlags result -> FreeRTOS bits representation.
3069//
3070// Success path: return the matched bits directly.
3071// Error path : return the caller-supplied snapshot of the flags word taken
3072// immediately after the timeout (or an aborted wait, see
3073// xTaskAbortDelay(): ERROR_CANCELED) was confirmed. FreeRTOS documents
3074// that xEventGroupWaitBits() returns the flags value *at the time
3075// of timeout*, not zero, so callers can distinguish which bits were
3076// set even though the wait condition was not fully satisfied.
3077// Pass snapshot = 0U for call sites where a timeout return of 0
3078// is the correct contract (e.g. xEventGroupSync).
3079static inline EventBits_t StkFlagsToFrtos(uint32_t result, EventBits_t snapshot = 0U)
3080{
3082 return snapshot; // timeout, aborted wait (ERROR_CANCELED) or error — return flags snapshot
3083
3084 return static_cast<EventBits_t>(result);
3085}
3086
3087// Build STK EventFlags options from FreeRTOS xWaitForAllBits / xClearOnExit.
3088static inline uint32_t BuildStkFlagsOpts(BaseType_t xClearOnExit,
3089 BaseType_t xWaitForAllBits)
3090{
3092
3093 if (xWaitForAllBits == pdTRUE)
3095
3096 if (xClearOnExit == pdFALSE)
3098
3099 return opts;
3100}
3101
3103{
3104 if (IsIrqContext())
3105 return nullptr;
3106
3108 return static_cast<EventGroupHandle_t>(eg);
3109}
3110
3112{
3113 if (pxEventGroupBuffer == nullptr)
3114 return nullptr;
3115
3116 if (IsIrqContext())
3117 return nullptr;
3118
3119 static_assert(sizeof(StaticEventGroup_t) >= sizeof(FrtosEventGroup),
3120 "StaticEventGroup_t is too small to hold FrtosEventGroup. "
3121 "Increase STATIC_EVENT_GROUP_TCB_SIZE_WORDS in freertos_stk.h.");
3122
3123 FrtosEventGroup *eg = new (pxEventGroupBuffer) FrtosEventGroup();
3124 eg->m_cb_owned = false; // caller owns the memory; vEventGroupDelete must not delete it
3125
3126 return static_cast<EventGroupHandle_t>(eg);
3127}
3128
3130{
3131 if (xEventGroup == nullptr)
3132 return;
3133
3134 ObjFree(static_cast<FrtosEventGroup *>(xEventGroup));
3135}
3136
3138 EventBits_t uxBitsToSet)
3139{
3140 if (xEventGroup == nullptr)
3141 return 0U;
3142
3143 uint32_t result = static_cast<FrtosEventGroup *>(xEventGroup)->m_ef.Set(uxBitsToSet);
3144 return StkFlagsToFrtos(result);
3145}
3146
3148 EventBits_t uxBitsToClear)
3149{
3150 if (xEventGroup == nullptr)
3151 return 0U;
3152
3153 // STK Clear() returns the value BEFORE clearing - matches FreeRTOS contract.
3154 uint32_t prev = static_cast<FrtosEventGroup *>(xEventGroup)->m_ef.Clear(uxBitsToClear);
3155 return StkFlagsToFrtos(prev);
3156}
3157
3159{
3160 if (xEventGroup == nullptr)
3161 return 0U;
3162
3163 return static_cast<EventBits_t>(static_cast<FrtosEventGroup *>(xEventGroup)->m_ef.Get());
3164}
3165
3167 EventBits_t uxBitsToWaitFor,
3168 BaseType_t xClearOnExit,
3169 BaseType_t xWaitForAllBits,
3170 TickType_t xTicksToWait)
3171{
3172 if (IsIrqContext() || (xEventGroup == nullptr) || (uxBitsToWaitFor == 0U))
3173 return 0U;
3174
3175 stk::sync::EventFlags &ef = static_cast<FrtosEventGroup *>(xEventGroup)->m_ef;
3176
3177 uint32_t opts = BuildStkFlagsOpts(xClearOnExit, xWaitForAllBits);
3178 uint32_t result = ef.Wait(static_cast<uint32_t>(uxBitsToWaitFor),
3179 opts,
3180 FrtosTimeoutToStk(xTicksToWait));
3181
3182 // On timeout, FreeRTOS documents that the return value is the flags word
3183 // at the moment the timeout occurred, not zero. ef.Get() is a volatile
3184 // read that is atomic on all supported 32-bit STK targets; no CS needed.
3185 const EventBits_t snapshot = static_cast<EventBits_t>(ef.Get());
3186 return StkFlagsToFrtos(result, snapshot);
3187}
3188
3190 EventBits_t uxBitsToSet,
3191 BaseType_t *pxHigherPriorityTaskWoken)
3192{
3193 if (xEventGroup == nullptr)
3194 return pdFAIL;
3195
3196 uint32_t result = static_cast<FrtosEventGroup *>(xEventGroup)->m_ef.Set(
3197 static_cast<uint32_t>(uxBitsToSet));
3198
3199 if (pxHigherPriorityTaskWoken != nullptr)
3200 *pxHigherPriorityTaskWoken = pdFALSE;
3201
3202 return stk::sync::EventFlags::IsError(result) ? pdFAIL : pdPASS;
3203}
3204
3206 EventBits_t uxBitsToClear)
3207{
3208 if (xEventGroup == nullptr)
3209 return 0U;
3210
3211 uint32_t prev = static_cast<FrtosEventGroup *>(xEventGroup)->m_ef.Clear(
3212 static_cast<uint32_t>(uxBitsToClear));
3213 return StkFlagsToFrtos(prev);
3214}
3215
3216// -----------------------------------------------------------------------------
3217// xEventGroupSync
3218//
3219// Design rationale
3220// ----------------
3221// FreeRTOS xEventGroupSync() is a multi-task rendezvous (barrier):
3222//
3223// 1. Atomically set this task's "I'm ready" bits.
3224// 2. Wait (with AND semantics) until ALL bits in uxBitsToWaitFor are set.
3225// 3. On success, atomically clear uxBitsToWaitFor and return the snapshot
3226// taken just before the clear.
3227//
3228// The set-then-wait sequence must appear atomic from every other task's point
3229// of view, so that no task can wake from step 2 before every peer has
3230// completed step 1.
3231//
3232// STK provides this guarantee naturally:
3233// - EventFlags::Set() acquires a ScopedCriticalSection, ORs the bits,
3234// calls ConditionVariable::NotifyAll() (which reschedules waiters but
3235// cannot actually run them until the CS is released), then exits the CS.
3236// - EventFlags::Wait() enters its own ScopedCriticalSection and loops,
3237// re-checking the predicate on every wakeup.
3238//
3239// Because both operations hold the same type of CS and the scheduler cannot
3240// pre-empt inside one, the sequence "set my bit -> enter wait loop" is
3241// indivisible: a waiter woken by our Set() will observe our bits already
3242// present when it re-evaluates its own predicate.
3243//
3244// Auto-clear policy
3245// -----------------
3246// The first task to find the AND predicate satisfied will attempt to clear
3247// uxBitsToWaitFor. Subsequent tasks that already passed the predicate (or
3248// are woken in the same tick) receive the snapshot value that was current
3249// when they sampled the flags (before any clear). This matches FreeRTOS
3250// behaviour: every unblocked task sees the full set of sync bits in its
3251// return value, and the clear is performed by the internal Wait() path under
3252// the critical section, making it race-free across concurrent waiters.
3253//
3254// If OPT_WAIT_ALL + (default) clear-on-exit are passed together to
3255// EventFlags::Wait(), the STK implementation clears exactly the matched
3256// bits (== uxBitsToWaitFor) when the predicate is first satisfied by each
3257// waiter independently — which is safe because all tasks wait for the same
3258// superset mask, and STK's "m_flags &= ~matched" inside Wait() is idempotent
3259// once the bits are already 0.
3260// -----------------------------------------------------------------------------
3261
3263 EventBits_t uxBitsToSet,
3264 EventBits_t uxBitsToWaitFor,
3265 TickType_t xTicksToWait)
3266{
3267 // Contract checks that mirror the FreeRTOS reference implementation.
3268 if (xEventGroup == nullptr) return 0U;
3269 if (uxBitsToWaitFor == 0U) return 0U;
3270 if (IsIrqContext()) return 0U; // blocking wait is ISR-unsafe
3271
3272 FrtosEventGroup *eg = static_cast<FrtosEventGroup *>(xEventGroup);
3273 stk::sync::EventFlags &ef = eg->m_ef;
3274
3275 // Step 1: Set this task's rendezvous bit(s).
3276 //
3277 // uxBitsToSet == 0 is legal (observer role: task waits without contributing
3278 // a bit). Only call Set() when there is actually something to set.
3279 if (uxBitsToSet != 0U)
3280 {
3281 uint32_t set_result = ef.Set(static_cast<uint32_t>(uxBitsToSet));
3282
3283 // Treat an ERROR_PARAMETER return as a hard usage fault — the caller
3284 // violated the API (e.g. set bit 31).
3286
3287 if (stk::sync::EventFlags::IsError(set_result))
3288 return 0U;
3289 }
3290
3291 // Step 2 + 3: Wait for ALL bits in uxBitsToWaitFor, clear them on success.
3292 //
3293 // OPT_WAIT_ALL — AND semantics: all bits must be set simultaneously.
3294 // OPT_NO_CLEAR is NOT passed — matched bits are cleared atomically inside
3295 // Wait() when the predicate is first satisfied, exactly matching the
3296 // FreeRTOS "clear bits on exit" contract for xEventGroupSync().
3297 const uint32_t opts = stk::sync::EventFlags::OPT_WAIT_ALL;
3298 // (OPT_NO_CLEAR absent -> clear on success)
3299
3300 uint32_t result = ef.Wait(static_cast<uint32_t>(uxBitsToWaitFor),
3301 opts,
3302 FrtosTimeoutToStk(xTicksToWait));
3303
3304 // On timeout or error, return 0 (matches FreeRTOS reference behaviour).
3305 return StkFlagsToFrtos(result);
3306}
3307
3308#endif // configUSE_EVENT_GROUPS
3309
3310// ===========================================================================
3311// Task Notification API — indexed implementation [configUSE_TASK_NOTIFICATIONS]
3312//
3313// Each FrtosTask carries m_notify[configTASK_NOTIFICATION_ARRAY_ENTRIES], a
3314// fixed-size array of NotifySlot structs. Every slot is fully independent:
3315// - m_notify[i].value : 32-bit notification word (eSetBits / eIncrement / etc.)
3316// - m_notify[i].pending : set-without-overwrite guard
3317// - m_notify[i].sem : stk::sync::Semaphore (binary) used as the blocking
3318// primitive; the non-indexed API used this exact model
3319// for slot 0.
3320//
3321// Non-indexed functions (xTaskNotifyGive, ulTaskNotifyTake, xTaskNotify,
3322// xTaskNotifyWait, xTaskNotifyFromISR) are thin forwarding wrappers to slot 0.
3323//
3324// Out-of-range slot index: assertion in debug builds, early-return/pdFAIL in
3325// release builds — matching FreeRTOS configASSERT behaviour.
3326// ===========================================================================
3327
3328#if configUSE_TASK_NOTIFICATIONS
3329
3330// -----------------------------------------------------------------------------
3331// Internal helper: validate slot index and resolve a NULL handle to self.
3332// Returns the FrtosTask pointer on success, nullptr on failure.
3333// -----------------------------------------------------------------------------
3335{
3337
3339 return nullptr;
3340
3341 if (xTask == nullptr)
3342 xTask = xTaskGetCurrentTaskHandle();
3343
3344 return static_cast<FrtosTask *>(xTask);
3345}
3346
3347// -----------------------------------------------------------------------------
3348// Internal helper: apply a notification action to a slot under a held CS.
3349// Returns pdPASS / pdFAIL (mirrors the public xTaskNotify contract).
3350// -----------------------------------------------------------------------------
3352 uint32_t ulValue,
3353 eNotifyAction eAction)
3354{
3355 switch (eAction)
3356 {
3357 case eNoAction:
3358 break;
3359
3360 case eSetBits:
3361 slot.value |= ulValue;
3362 break;
3363
3364 case eIncrement:
3365 slot.value++;
3366 break;
3367
3369 slot.value = ulValue;
3370 break;
3371
3373 if (slot.pending)
3374 return pdFAIL; // value not consumed yet
3375 slot.value = ulValue;
3376 slot.pending = true;
3377 break;
3378
3379 default:
3380 return pdFAIL;
3381 }
3382
3383 return pdPASS;
3384}
3385
3386// ===========================================================================
3387// Indexed API
3388// ===========================================================================
3389
3391 UBaseType_t uxIndexToNotify)
3392{
3393 FrtosTask *t = ResolveNotifyTarget(xTaskToNotify, uxIndexToNotify);
3394 if (t == nullptr)
3395 return pdFAIL;
3396
3397 t->m_notify[uxIndexToNotify].sem.Signal(); // ISR-safe
3398 return pdPASS;
3399}
3400
3402 BaseType_t ulClearCountOnExit,
3403 TickType_t xTicksToWait)
3404{
3405 if (IsIrqContext())
3406 return 0U;
3407
3408 FrtosTask *t = ResolveNotifyTarget(nullptr, uxIndexToWait);
3409 if (t == nullptr)
3410 return 0U;
3411
3412 FrtosTask::NotifySlot &slot = t->m_notify[uxIndexToWait];
3413
3414 if (!slot.sem.Wait(FrtosTimeoutToStk(xTicksToWait)))
3415 return 0U;
3416
3417 uint32_t val = 0U;
3418
3419 {
3421 val = slot.value;
3422
3423 if (ulClearCountOnExit == pdTRUE)
3424 slot.value = 0U;
3425 else
3426 if (slot.value > 0U)
3427 slot.value--;
3428 }
3429
3430 return val + 1U; // +1: the semaphore signal itself counts
3431}
3432
3434 UBaseType_t uxIndexToNotify,
3435 uint32_t ulValue,
3436 eNotifyAction eAction)
3437{
3438 FrtosTask *t = ResolveNotifyTarget(xTaskToNotify, uxIndexToNotify);
3439 if (t == nullptr)
3440 return pdFAIL;
3441
3442 FrtosTask::NotifySlot &slot = t->m_notify[uxIndexToNotify];
3443 BaseType_t result = pdPASS;
3444
3445 {
3447 result = NotifyApplyAction(slot, ulValue, eAction);
3448 }
3449
3450 if (result == pdPASS)
3451 slot.sem.Signal();
3452
3453 return result;
3454}
3455
3457 uint32_t ulBitsToClearOnEntry,
3458 uint32_t ulBitsToClearOnExit,
3459 uint32_t *pulNotificationValue,
3460 TickType_t xTicksToWait)
3461{
3462 if (IsIrqContext())
3463 return pdFAIL;
3464
3465 FrtosTask *t = ResolveNotifyTarget(nullptr, uxIndexToWait);
3466 if (t == nullptr)
3467 return pdFAIL;
3468
3469 FrtosTask::NotifySlot &slot = t->m_notify[uxIndexToWait];
3470
3471 // Clear entry bits before blocking.
3472 {
3474 slot.value &= ~ulBitsToClearOnEntry;
3475 }
3476
3477 // Block until notified or timeout.
3478 if (!slot.sem.Wait(FrtosTimeoutToStk(xTicksToWait)))
3479 return pdFAIL;
3480
3481 // Read and apply exit-clear.
3482 {
3484
3485 if (pulNotificationValue != nullptr)
3486 *pulNotificationValue = slot.value;
3487
3488 slot.value &= ~ulBitsToClearOnExit;
3489 slot.pending = false;
3490 }
3491
3492 return pdPASS;
3493}
3494
3496 UBaseType_t uxIndexToNotify,
3497 uint32_t ulValue,
3498 eNotifyAction eAction,
3499 BaseType_t *pxHigherPriorityTaskWoken)
3500{
3501 BaseType_t result = xTaskNotifyIndexed(xTaskToNotify, uxIndexToNotify, ulValue, eAction);
3502
3503 if (pxHigherPriorityTaskWoken != nullptr)
3504 *pxHigherPriorityTaskWoken = pdFALSE;
3505
3506 return result;
3507}
3508
3509// ===========================================================================
3510// Non-indexed API — thin wrappers around slot 0
3511// ===========================================================================
3512
3514{
3515 return xTaskNotifyGiveIndexed(xTaskToNotify, 0U);
3516}
3517
3518uint32_t ulTaskNotifyTake(BaseType_t ulClearCountOnExit, TickType_t xTicksToWait)
3519{
3520 return ulTaskNotifyTakeIndexed(0U, ulClearCountOnExit, xTicksToWait);
3521}
3522
3524 uint32_t ulValue,
3525 eNotifyAction eAction)
3526{
3527 return xTaskNotifyIndexed(xTaskToNotify, 0U, ulValue, eAction);
3528}
3529
3530BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry,
3531 uint32_t ulBitsToClearOnExit,
3532 uint32_t *pulNotificationValue,
3533 TickType_t xTicksToWait)
3534{
3535 return xTaskNotifyWaitIndexed(0U, ulBitsToClearOnEntry, ulBitsToClearOnExit,
3536 pulNotificationValue, xTicksToWait);
3537}
3538
3540 uint32_t ulValue,
3541 eNotifyAction eAction,
3542 BaseType_t *pxHigherPriorityTaskWoken)
3543{
3544 return xTaskNotifyFromISRIndexed(xTaskToNotify, 0U, ulValue, eAction,
3545 pxHigherPriorityTaskWoken);
3546}
3547
3548// ===========================================================================
3549// Task Notification — AndQuery / StateClear / ValueClear extensions
3550//
3551// xTaskNotifyAndQuery[Indexed]
3552// Like xTaskNotify[Indexed] but additionally snapshots the slot value
3553// *before* the action is applied and writes it to *pulPreviousNotifyValue.
3554// This gives the caller an atomic read-modify-notify without a separate
3555// critical section in application code.
3556//
3557// xTaskNotifyAndQueryFromISR[Indexed]
3558// ISR-safe wrappers around the Indexed variant; pxHigherPriorityTaskWoken
3559// is always set to pdFALSE (STK handles the context switch internally).
3560//
3561// xTaskNotifyStateClear[Indexed]
3562// Clears the pending flag of the selected slot and drains the binary
3563// semaphore (one TryWait() is sufficient because max_count = 1).
3564// Returns pdTRUE when a notification was pending, pdFALSE otherwise.
3565// The notification value word is NOT modified — only the pending state.
3566//
3567// ulTaskNotifyValueClear[Indexed]
3568// Atomically clears the specified bits in slot.value (under a critical
3569// section) and returns the value *before* the clear. The pending flag
3570// and semaphore are not touched.
3571// ===========================================================================
3572
3573// -----------------------------------------------------------------------------
3574// xTaskNotifyAndQueryIndexed
3575// -----------------------------------------------------------------------------
3576
3578 UBaseType_t uxIndexToNotify,
3579 uint32_t ulValue,
3580 eNotifyAction eAction,
3581 uint32_t *pulPreviousNotifyValue)
3582{
3583 FrtosTask *t = ResolveNotifyTarget(xTaskToNotify, uxIndexToNotify);
3584 if (t == nullptr)
3585 return pdFAIL;
3586
3587 FrtosTask::NotifySlot &slot = t->m_notify[uxIndexToNotify];
3588 BaseType_t result = pdPASS;
3589
3590 {
3592
3593 // Snapshot the value *before* the action — this is the only difference
3594 // from xTaskNotifyIndexed.
3595 if (pulPreviousNotifyValue != nullptr)
3596 *pulPreviousNotifyValue = slot.value;
3597
3598 result = NotifyApplyAction(slot, ulValue, eAction);
3599 }
3600
3601 if (result == pdPASS)
3602 slot.sem.Signal();
3603
3604 return result;
3605}
3606
3607// -----------------------------------------------------------------------------
3608// xTaskNotifyAndQuery (slot 0 wrapper)
3609// -----------------------------------------------------------------------------
3610
3612 uint32_t ulValue,
3613 eNotifyAction eAction,
3614 uint32_t *pulPreviousNotifyValue)
3615{
3616 return xTaskNotifyAndQueryIndexed(xTaskToNotify, 0U, ulValue, eAction,
3617 pulPreviousNotifyValue);
3618}
3619
3620// -----------------------------------------------------------------------------
3621// xTaskNotifyAndQueryFromISRIndexed
3622// -----------------------------------------------------------------------------
3623
3625 UBaseType_t uxIndexToNotify,
3626 uint32_t ulValue,
3627 eNotifyAction eAction,
3628 uint32_t *pulPreviousNotifyValue,
3629 BaseType_t *pxHigherPriorityTaskWoken)
3630{
3631 BaseType_t result = xTaskNotifyAndQueryIndexed(xTaskToNotify, uxIndexToNotify,
3632 ulValue, eAction,
3633 pulPreviousNotifyValue);
3634
3635 if (pxHigherPriorityTaskWoken != nullptr)
3636 *pxHigherPriorityTaskWoken = pdFALSE; // STK handles the context switch internally
3637
3638 return result;
3639}
3640
3641// -----------------------------------------------------------------------------
3642// xTaskNotifyAndQueryFromISR (slot 0 wrapper)
3643// -----------------------------------------------------------------------------
3644
3646 uint32_t ulValue,
3647 eNotifyAction eAction,
3648 uint32_t *pulPreviousNotifyValue,
3649 BaseType_t *pxHigherPriorityTaskWoken)
3650{
3651 return xTaskNotifyAndQueryFromISRIndexed(xTaskToNotify, 0U, ulValue, eAction,
3652 pulPreviousNotifyValue,
3653 pxHigherPriorityTaskWoken);
3654}
3655
3656// -----------------------------------------------------------------------------
3657// xTaskNotifyStateClearIndexed
3658//
3659// Clears the pending notification state for the specified slot.
3660//
3661// FreeRTOS contract:
3662// - Returns pdTRUE if a notification was pending (slot was "notified").
3663// - Returns pdFALSE if no notification was pending.
3664// - The 32-bit value word is NOT modified; only the pending/signaled state.
3665//
3666// STK mapping:
3667// The "pending" state is represented by two cooperating members:
3668// slot.pending — set by eSetValueWithoutOverwrite; guards value overwrite.
3669// slot.sem — binary semaphore; its count > 0 means a notification
3670// arrived and has not yet been consumed by Wait/Take.
3671// Both are cleared here atomically under a critical section so that a
3672// concurrent xTaskNotifyWait or ulTaskNotifyTake cannot race with the clear.
3673//
3674// The semaphore is binary (max_count = 1), so one TryWait() is sufficient
3675// to drain it. We must not call sem.Wait() (blocking) from either task or
3676// ISR context — TryWait() (NO_WAIT, ISR-safe) is the correct choice here.
3677// -----------------------------------------------------------------------------
3678
3680 UBaseType_t uxIndexToClear)
3681{
3682 FrtosTask *t = ResolveNotifyTarget(xTask, uxIndexToClear);
3683 if (t == nullptr)
3684 return pdFALSE;
3685
3686 FrtosTask::NotifySlot &slot = t->m_notify[uxIndexToClear];
3687
3689
3690 // Determine whether a notification was pending before we clear anything.
3691 // A notification is considered "pending" when the semaphore has a count
3692 // (i.e. Signal() was called but Wait/Take has not yet consumed it) OR
3693 // when the value-without-overwrite guard flag is set.
3694 const bool was_pending = (slot.sem.GetCount() != 0U) || slot.pending;
3695
3696 if (was_pending)
3697 {
3698 // Drain the semaphore (binary, at most 1 token to consume).
3699 slot.sem.TryWait();
3700
3701 // Clear the eSetValueWithoutOverwrite pending guard so that a
3702 // subsequent xTaskNotify(eSetValueWithoutOverwrite) can write a new
3703 // value without being rejected.
3704 slot.pending = false;
3705 }
3706
3707 return was_pending ? pdTRUE : pdFALSE;
3708}
3709
3710// -----------------------------------------------------------------------------
3711// xTaskNotifyStateClear (slot 0 wrapper)
3712// -----------------------------------------------------------------------------
3713
3718
3719// -----------------------------------------------------------------------------
3720// ulTaskNotifyValueClearIndexed
3721//
3722// Atomically clears the specified bits in the slot's 32-bit value word and
3723// returns the value *before* the clear.
3724//
3725// FreeRTOS contract:
3726// - Performs: old = slot.value; slot.value &= ~ulBitsToClear; return old;
3727// - The pending flag and semaphore are NOT touched.
3728// - Pass ulBitsToClear = 0xFFFFFFFF to clear all bits.
3729// -----------------------------------------------------------------------------
3730
3732 UBaseType_t uxIndexToClear,
3733 uint32_t ulBitsToClear)
3734{
3735 FrtosTask *t = ResolveNotifyTarget(xTask, uxIndexToClear);
3736 if (t == nullptr)
3737 return 0U;
3738
3739 FrtosTask::NotifySlot &slot = t->m_notify[uxIndexToClear];
3740
3742
3743 const uint32_t prev = slot.value;
3744 slot.value &= ~ulBitsToClear;
3745
3746 return prev;
3747}
3748
3749// -----------------------------------------------------------------------------
3750// ulTaskNotifyValueClear (slot 0 wrapper)
3751// -----------------------------------------------------------------------------
3752
3754 uint32_t ulBitsToClear)
3755{
3756 return ulTaskNotifyValueClearIndexed(xTask, 0U, ulBitsToClear);
3757}
3758
3759#endif // configUSE_TASK_NOTIFICATIONS
3760
3761// ===========================================================================
3762// Thread-local storage (TLS) API
3763//
3764// Each FrtosTask carries a fixed-size m_tls[] array of void* slots
3765// (configNUM_THREAD_LOCAL_STORAGE_POINTERS entries, zero-initialised).
3766// The task to operate on is resolved via its TaskHandle_t, which is the
3767// FrtosTask* cast to void*. NULL resolves to the calling task via
3768// xTaskGetCurrentTaskHandle().
3769//
3770// STK TLS note:
3771// stk::hw::GetTlsPtr / SetTlsPtr manage the kernel's own per-task context
3772// pointer (used internally by the scheduler). Application TLS lives in
3773// the m_tls[] member of FrtosTask and does NOT touch the STK TP register,
3774// preserving the kernel's internal invariants.
3775// ===========================================================================
3776
3778 BaseType_t xIndex,
3779 void *pvValue)
3780{
3781 if (xIndex < 0 || static_cast<size_t>(xIndex) >= configNUM_THREAD_LOCAL_STORAGE_POINTERS)
3782 return;
3783
3784 // Resolve NULL -> calling task.
3785 if (xTaskToSet == nullptr)
3786 xTaskToSet = xTaskGetCurrentTaskHandle();
3787
3788 if (xTaskToSet == nullptr)
3789 return;
3790
3791 FrtosTask *t = static_cast<FrtosTask *>(xTaskToSet);
3792
3793 // A critical section is not strictly required for a pointer-sized store on
3794 // aligned memory on single-issue cores, but we guard anyway for strict
3795 // correctness on SMP targets (RP2040, dual-core Cortex-M33).
3797 t->m_tls[static_cast<size_t>(xIndex)] = pvValue;
3798}
3799
3801 BaseType_t xIndex)
3802{
3803 if (xIndex < 0 || static_cast<size_t>(xIndex) >= configNUM_THREAD_LOCAL_STORAGE_POINTERS)
3804 return nullptr;
3805
3806 // Resolve NULL -> calling task.
3807 if (xTaskToQuery == nullptr)
3808 xTaskToQuery = xTaskGetCurrentTaskHandle();
3809
3810 if (xTaskToQuery == nullptr)
3811 return nullptr;
3812
3813 const FrtosTask *t = static_cast<const FrtosTask *>(xTaskToQuery);
3814
3816 return t->m_tls[static_cast<size_t>(xIndex)];
3817}
3818
3819// ===========================================================================
3820// Stream Buffer API [configUSE_STREAM_BUFFERS]
3821//
3822// FrtosStreamBuffer wraps stk::sync::Pipe (element_size = 1), using it as a
3823// byte ring-buffer. sync::Pipe is preferred over sync::MessageQueue here
3824// because it provides WriteBulk / ReadBulk / TryWriteBulk / TryReadBulk
3825// natively, enabling efficient multi-byte transfers and clean trigger-level
3826// blocking without any single-byte Get() workarounds.
3827//
3828// Send -> Pipe::WriteBulk() (blocking) / TryWriteBulk() (ISR, NO_WAIT)
3829// Receive -> Pipe::ReadBulk() with trigger-level gating (see below)
3830// Pipe::TryReadBulk() (ISR, NO_WAIT)
3831//
3832// Trigger level (xStreamBufferReceive only):
3833// When m_trigger > 1 and the caller requested a blocking wait, Receive()
3834// calls ReadBulk(dst, m_trigger, timeout) to block atomically until at
3835// least m_trigger bytes are consumed, then TryReadBulk() drains the rest
3836// of the caller's buffer non-blocking. This reuses Pipe's own CV wait
3837// loop — no busy-spin, no lost-wakeup race, and no single-byte iteration.
3838// ===========================================================================
3839
3840#if configUSE_STREAM_BUFFERS
3841
3842static_assert(sizeof(StaticStreamBuffer_t) >= sizeof(FrtosStreamBuffer),
3843 "Increase STATIC_STREAM_BUFFER_TCB_SIZE_WORDS in FreeRTOS.h.");
3844
3846 size_t xTriggerLevelBytes)
3847{
3848 if (xBufferSizeBytes == 0U)
3849 return nullptr;
3850
3851 uint8_t *buf = ObjAllocArray<uint8_t>(xBufferSizeBytes);
3852 if (buf == nullptr)
3853 return nullptr;
3854
3855 FrtosStreamBuffer *sb = ObjAlloc<FrtosStreamBuffer>(buf, xBufferSizeBytes, xTriggerLevelBytes);
3856 if (sb == nullptr)
3857 {
3858 ObjFreeArray(buf);
3859 return nullptr;
3860 }
3861
3862 sb->m_buf_owned = true;
3863 sb->m_cb_owned = true;
3864
3865 return static_cast<StreamBufferHandle_t>(sb);
3866}
3867
3869 size_t xBufferSizeBytes,
3870 size_t xTriggerLevelBytes,
3871 uint8_t *pucStreamBufferStorageArea,
3872 StaticStreamBuffer_t *pxStaticStreamBuffer)
3873{
3874 if ((pucStreamBufferStorageArea == nullptr) ||
3875 (pxStaticStreamBuffer == nullptr) ||
3876 (xBufferSizeBytes == 0U))
3877 return nullptr;
3878
3879 static_assert(sizeof(StaticStreamBuffer_t) >= sizeof(FrtosStreamBuffer),
3880 "Increase STATIC_STREAM_BUFFER_TCB_SIZE_WORDS in FreeRTOS.h.");
3881
3882 FrtosStreamBuffer *sb = new (pxStaticStreamBuffer)
3883 FrtosStreamBuffer(pucStreamBufferStorageArea,
3884 xBufferSizeBytes,
3885 xTriggerLevelBytes);
3886
3887 // m_buf_owned = false, m_cb_owned = false already set by the ctor
3888 return static_cast<StreamBufferHandle_t>(sb);
3889}
3890
3892{
3893 if (xStreamBuffer == nullptr)
3894 return;
3895
3896 FrtosStreamBuffer *sb = static_cast<FrtosStreamBuffer *>(xStreamBuffer);
3897
3898 ObjFree(sb);
3899}
3900
3902 const void *pvTxData,
3903 size_t xDataLengthBytes,
3904 TickType_t xTicksToWait)
3905{
3906 if ((xStreamBuffer == nullptr) || (pvTxData == nullptr) || (xDataLengthBytes == 0U))
3907 return 0U;
3908
3909 FrtosStreamBuffer *sb = static_cast<FrtosStreamBuffer *>(xStreamBuffer);
3910
3911 const size_t sent = sb->m_pipe.WriteBulk(
3912 static_cast<const uint8_t *>(pvTxData),
3913 xDataLengthBytes,
3914 FrtosTimeoutToStk(xTicksToWait));
3915
3916 // Fire send-complete callback outside any critical section.
3917 if ((sent > 0U) && (sb->m_send_cb != nullptr))
3918 {
3919 BaseType_t woken = pdFALSE;
3920 sb->m_send_cb(xStreamBuffer, &woken);
3921 }
3922
3923 return sent;
3924}
3925
3927 const void *pvTxData,
3928 size_t xDataLengthBytes,
3929 BaseType_t *pxHigherPriorityTaskWoken)
3930{
3931 if (pxHigherPriorityTaskWoken != nullptr)
3932 *pxHigherPriorityTaskWoken = pdFALSE;
3933
3934 if ((xStreamBuffer == nullptr) || (pvTxData == nullptr) || (xDataLengthBytes == 0U))
3935 return 0U;
3936
3937 FrtosStreamBuffer *sb = static_cast<FrtosStreamBuffer *>(xStreamBuffer);
3938
3939 const size_t sent = sb->m_pipe.TryWriteBulk(
3940 static_cast<const uint8_t *>(pvTxData),
3941 xDataLengthBytes);
3942
3943 // Fire send-complete callback outside any critical section.
3944 // pxHigherPriorityTaskWoken is always pdFALSE per STK convention.
3945 if ((sent > 0U) && (sb->m_send_cb != nullptr))
3946 {
3947 BaseType_t woken = pdFALSE;
3948 sb->m_send_cb(xStreamBuffer, &woken);
3949 }
3950
3951 return sent;
3952}
3953
3955 void *pvRxData,
3956 size_t xBufferLengthBytes,
3957 TickType_t xTicksToWait)
3958{
3959 if ((xStreamBuffer == nullptr) || (pvRxData == nullptr) || (xBufferLengthBytes == 0U))
3960 return 0U;
3961
3962 if (IsIrqContext() && (xTicksToWait != 0U))
3963 return 0U;
3964
3965 FrtosStreamBuffer *sb = static_cast<FrtosStreamBuffer *>(xStreamBuffer);
3966
3967 // ReadBulkTriggered handles all cases in one call:
3968 // - NO_WAIT (xTicksToWait == 0): trigger not enforced, returns whatever
3969 // is available immediately (guaranteed by CV NO_WAIT fast-path).
3970 // - Blocking with trigger: waits until m_trigger bytes are present, then
3971 // drains up to xBufferLengthBytes in one atomic CS pass.
3972 // - Timeout before trigger: drains whatever arrived, possibly 0.
3973 const size_t total = sb->m_pipe.ReadBulkTriggered(
3974 static_cast<uint8_t *>(pvRxData),
3975 sb->m_trigger,
3976 xBufferLengthBytes,
3977 FrtosTimeoutToStk(xTicksToWait));
3978
3979 // Fire receive-complete callback outside any critical section.
3980 if ((total > 0U) && (sb->m_recv_cb != nullptr))
3981 {
3982 BaseType_t woken = pdFALSE;
3983 sb->m_recv_cb(xStreamBuffer, &woken);
3984 }
3985
3986 return total;
3987}
3988
3990 void *pvRxData,
3991 size_t xBufferLengthBytes,
3992 BaseType_t *pxHigherPriorityTaskWoken)
3993{
3994 if (pxHigherPriorityTaskWoken != nullptr)
3995 *pxHigherPriorityTaskWoken = pdFALSE;
3996
3997 if ((xStreamBuffer == nullptr) || (pvRxData == nullptr) || (xBufferLengthBytes == 0U))
3998 return 0U;
3999
4000 FrtosStreamBuffer *sb = static_cast<FrtosStreamBuffer *>(xStreamBuffer);
4001
4002 const size_t received = sb->m_pipe.TryReadBulk(
4003 static_cast<uint8_t *>(pvRxData),
4004 xBufferLengthBytes);
4005
4006 // Fire receive-complete callback outside any critical section.
4007 // pxHigherPriorityTaskWoken is always pdFALSE per STK convention.
4008 if ((received > 0U) && (sb->m_recv_cb != nullptr))
4009 {
4010 BaseType_t woken = pdFALSE;
4011 sb->m_recv_cb(xStreamBuffer, &woken);
4012 }
4013
4014 return received;
4015}
4016
4018{
4019 if (xStreamBuffer == nullptr)
4020 return 0U;
4021
4022 return static_cast<FrtosStreamBuffer *>(xStreamBuffer)->m_pipe.GetCount();
4023}
4024
4026{
4027 if (xStreamBuffer == nullptr)
4028 return 0U;
4029
4030 return static_cast<FrtosStreamBuffer *>(xStreamBuffer)->m_pipe.GetSpace();
4031}
4032
4034{
4035 return (xStreamBufferBytesAvailable(xStreamBuffer) == 0U) ? pdTRUE : pdFALSE;
4036}
4037
4039{
4040 return (xStreamBufferSpacesAvailable(xStreamBuffer) == 0U) ? pdTRUE : pdFALSE;
4041}
4042
4044{
4045 if (xStreamBuffer == nullptr)
4046 return pdFAIL;
4047
4048 static_cast<FrtosStreamBuffer *>(xStreamBuffer)->m_pipe.Reset();
4049 return pdPASS;
4050}
4051
4053 size_t xTriggerLevelBytes)
4054{
4055 if (xStreamBuffer == nullptr)
4056 return pdFALSE;
4057
4058 FrtosStreamBuffer *sb = static_cast<FrtosStreamBuffer *>(xStreamBuffer);
4059
4060 if (xTriggerLevelBytes > sb->m_pipe.GetCapacity())
4061 return pdFALSE;
4062
4064 sb->m_trigger = (xTriggerLevelBytes >= 1U ? xTriggerLevelBytes : 1U);
4065
4066 return pdTRUE;
4067}
4068
4070 BaseType_t *pxHigherPriorityTaskWoken)
4071{
4072 if (pxHigherPriorityTaskWoken != nullptr)
4073 *pxHigherPriorityTaskWoken = pdFALSE;
4074
4075 if (xStreamBuffer == nullptr)
4076 return pdFAIL;
4077
4078 // Pipe::Reset() acquires ScopedCriticalSection internally — ISR-safe.
4079 static_cast<FrtosStreamBuffer *>(xStreamBuffer)->m_pipe.Reset();
4080 return pdPASS;
4081}
4082
4084{
4085 if (xStreamBuffer == nullptr)
4086 return 0U;
4087
4088 // m_trigger is a plain size_t written only under ScopedCriticalSection
4089 // (in xStreamBufferSetTriggerLevel). A size_t-aligned read is atomic on
4090 // all Cortex-M targets — same rationale as Pipe::GetCount().
4091 return static_cast<const FrtosStreamBuffer *>(xStreamBuffer)->m_trigger;
4092}
4093
4094// -----------------------------------------------------------------------------
4095// xStreamBufferNextMessageLengthBytes
4096// -----------------------------------------------------------------------------
4097// A stream buffer carries an unframed byte sequence with no length-prefix
4098// headers, so there is no concept of a discrete "next message" boundary.
4099// The FreeRTOS reference implementation therefore defines
4100// xStreamBufferNextMessageLengthBytes() for stream buffers as identical to
4101// xStreamBufferBytesAvailable(): it returns the total number of bytes that
4102// could be consumed by the next xStreamBufferReceive() call without blocking.
4103//
4104// The function exists primarily so that code written against the combined
4105// stream/message buffer API can call the same introspection function on both
4106// handle types without a type-discriminating branch.
4107// -----------------------------------------------------------------------------
4108
4110{
4111 // Delegates to xStreamBufferBytesAvailable() which performs the NULL guard
4112 // and returns Pipe::GetCount() — ISR-safe on all supported targets.
4113 return xStreamBufferBytesAvailable(xStreamBuffer);
4114}
4115
4116// -----------------------------------------------------------------------------
4117// xStreamBufferCreateWithCallback / xStreamBufferCreateStaticWithCallback
4118//
4119// Identical to xStreamBufferCreate / xStreamBufferCreateStatic but store two
4120// optional per-instance notification callbacks (m_send_cb, m_recv_cb) in
4121// FrtosStreamBuffer. Callbacks fire at the end of every successful Send /
4122// Receive (including ISR variants), outside any critical section.
4123// -----------------------------------------------------------------------------
4124
4126 size_t xBufferSizeBytes,
4127 size_t xTriggerLevelBytes,
4128 StreamBufferCallbackFunction_t pxSendCompletedCallback,
4129 StreamBufferCallbackFunction_t pxReceiveCompletedCallback)
4130{
4131 if (xBufferSizeBytes == 0U)
4132 return nullptr;
4133
4134 uint8_t *buf = ObjAllocArray<uint8_t>(xBufferSizeBytes);
4135 if (buf == nullptr)
4136 return nullptr;
4137
4139 buf,
4140 xBufferSizeBytes,
4141 xTriggerLevelBytes,
4142 pxSendCompletedCallback,
4143 pxReceiveCompletedCallback);
4144
4145 if (sb == nullptr)
4146 {
4147 ObjFreeArray(buf);
4148 return nullptr;
4149 }
4150
4151 sb->m_buf_owned = true;
4152 sb->m_cb_owned = true;
4153
4154 return static_cast<StreamBufferHandle_t>(sb);
4155}
4156
4158 size_t xBufferSizeBytes,
4159 size_t xTriggerLevelBytes,
4160 uint8_t *pucStreamBufferStorageArea,
4161 StaticStreamBuffer_t *pxStaticStreamBuffer,
4162 StreamBufferCallbackFunction_t pxSendCompletedCallback,
4163 StreamBufferCallbackFunction_t pxReceiveCompletedCallback)
4164{
4165 if ((pucStreamBufferStorageArea == nullptr) ||
4166 (pxStaticStreamBuffer == nullptr) ||
4167 (xBufferSizeBytes == 0U))
4168 return nullptr;
4169
4170 static_assert(sizeof(StaticStreamBuffer_t) >= sizeof(FrtosStreamBuffer),
4171 "Increase STATIC_STREAM_BUFFER_TCB_SIZE_WORDS in FreeRTOS.h.");
4172
4173 FrtosStreamBuffer *sb = new (pxStaticStreamBuffer)
4174 FrtosStreamBuffer(pucStreamBufferStorageArea,
4175 xBufferSizeBytes,
4176 xTriggerLevelBytes,
4177 pxSendCompletedCallback,
4178 pxReceiveCompletedCallback);
4179 // m_buf_owned = false, m_cb_owned = false already set by ctor
4180 return static_cast<StreamBufferHandle_t>(sb);
4181}
4182
4183// ===========================================================================
4184// Message Buffer API
4185//
4186// FrtosMessageBuffer uses two STK primitives:
4187//
4188// m_pool (BlockMemoryPool) — payload blocks of AlignBlockSize(max_msg_size).
4189// m_eq (MessageQueue) — envelope FIFO: each slot holds a MsgEnvelope
4190// {size_t len; void* blk}.
4191//
4192// Send:
4193// 1. TimedAlloc() a block from m_pool (blocks if pool is empty).
4194// 2. memcpy payload into the block.
4195// 3. Put({len, blk}) envelope into m_eq (blocks if eq is full — which can
4196// only happen if TimedAlloc succeeded, i.e. m_pool and m_eq are always
4197// in sync: one envelope slot per pool block).
4198//
4199// Receive:
4200// 1. Get() envelope from m_eq (blocks if empty).
4201// 2. memcpy payload out of the block.
4202// 3. Free() the block back to m_pool — wakes one blocked sender if any.
4203//
4204// Reset:
4205// Drain all envelopes, free their blocks, then Reset() the eq.
4206// ===========================================================================
4207
4208static_assert(sizeof(StaticMessageBuffer_t) >= sizeof(FrtosMessageBuffer),
4209 "Increase STATIC_MESSAGE_BUFFER_TCB_SIZE_WORDS in FreeRTOS.h.");
4210
4211// Derive slot count from a flat byte budget.
4212static size_t MsgBufSlotCount(size_t budget_bytes, size_t max_msg_size)
4213{
4214 const size_t block_size = stk::memory::BlockMemoryPool::AlignBlockSize(max_msg_size);
4215 const size_t slot_cost = block_size + FrtosMessageBuffer::ENVELOPE_SIZE;
4216
4217 if (slot_cost == 0U)
4218 return 0U;
4219
4220 return budget_bytes / slot_cost;
4221}
4222
4224 size_t xMaxMessageSize)
4225{
4226 if ((xBufferSizeBytes == 0U) || (xMaxMessageSize == 0U))
4227 return nullptr;
4228
4229 const size_t count = MsgBufSlotCount(xBufferSizeBytes, xMaxMessageSize);
4230
4231 if (count == 0U)
4232 return nullptr;
4233
4234 FrtosMessageBuffer *mb = ObjAlloc<FrtosMessageBuffer>(xMaxMessageSize, count);
4235
4236 if (mb == nullptr)
4237 return nullptr;
4238
4239 if (!mb->m_pool.IsStorageValid() || !mb->m_eq.IsStorageValid())
4240 {
4241 ObjFreeRaw(mb);
4242 return nullptr;
4243 }
4244
4245 return static_cast<MessageBufferHandle_t>(mb);
4246}
4247
4249 size_t xMaxMessageSize,
4250 size_t xMessageCount,
4251 uint8_t *pucMessageBufferStorageArea,
4252 StaticMessageBuffer_t *pxStaticMessageBuffer)
4253{
4254 if ((pucMessageBufferStorageArea == nullptr) ||
4255 (pxStaticMessageBuffer == nullptr) ||
4256 (xMaxMessageSize == 0U) ||
4257 (xMessageCount == 0U))
4258 return nullptr;
4259
4260 const size_t block_size = stk::memory::BlockMemoryPool::AlignBlockSize(xMaxMessageSize);
4261 const size_t storage_size = xMessageCount * (block_size + FrtosMessageBuffer::ENVELOPE_SIZE);
4262
4263 FrtosMessageBuffer *mb = new (pxStaticMessageBuffer)
4264 FrtosMessageBuffer(xMaxMessageSize, xMessageCount,
4265 pucMessageBufferStorageArea, storage_size);
4266
4267 return static_cast<MessageBufferHandle_t>(mb);
4268}
4269
4270// -----------------------------------------------------------------------------
4271// xMessageBufferCreateWithCallback / xMessageBufferCreateStaticWithCallback
4272//
4273// Identical to xMessageBufferCreate / xMessageBufferCreateStatic but store two
4274// optional per-instance notification callbacks (m_send_cb, m_recv_cb) in
4275// FrtosMessageBuffer. Callbacks fire at the end of every successful Send /
4276// Receive (including ISR variants), outside any critical section.
4277// The handle passed to each callback is the MessageBufferHandle_t cast to
4278// StreamBufferHandle_t, matching the FreeRTOS convention for this callback type.
4279// -----------------------------------------------------------------------------
4280
4282 size_t xBufferSizeBytes,
4283 size_t xMaxMessageSize,
4284 StreamBufferCallbackFunction_t pxSendCompletedCallback,
4285 StreamBufferCallbackFunction_t pxReceiveCompletedCallback)
4286{
4287 if ((xBufferSizeBytes == 0U) || (xMaxMessageSize == 0U))
4288 return nullptr;
4289
4290 const size_t block_size = stk::memory::BlockMemoryPool::AlignBlockSize(xMaxMessageSize);
4291 const size_t slot_cost = block_size + FrtosMessageBuffer::ENVELOPE_SIZE;
4292 const size_t count = xBufferSizeBytes / slot_cost;
4293
4294 if (count == 0U)
4295 return nullptr;
4296
4298 xMaxMessageSize, count,
4299 pxSendCompletedCallback, pxReceiveCompletedCallback);
4300
4301 if (mb == nullptr)
4302 return nullptr;
4303
4304 // Detect heap failure in the envelope buffer allocation performed by the
4305 // heap constructor (m_eq is backed by a separately ObjAllocArray'd byte array).
4306 if (!mb->m_eq.IsStorageValid())
4307 {
4308 ObjFreeRaw(mb);
4309 return nullptr;
4310 }
4311
4312 return static_cast<MessageBufferHandle_t>(mb);
4313}
4314
4316 size_t xMaxMessageSize,
4317 size_t xMessageCount,
4318 uint8_t *pucMessageBufferStorageArea,
4319 StaticMessageBuffer_t *pxStaticMessageBuffer,
4320 StreamBufferCallbackFunction_t pxSendCompletedCallback,
4321 StreamBufferCallbackFunction_t pxReceiveCompletedCallback)
4322{
4323 if ((pucMessageBufferStorageArea == nullptr) ||
4324 (pxStaticMessageBuffer == nullptr) ||
4325 (xMaxMessageSize == 0U) ||
4326 (xMessageCount == 0U))
4327 return nullptr;
4328
4329 static_assert(sizeof(StaticMessageBuffer_t) >= sizeof(FrtosMessageBuffer),
4330 "Increase STATIC_MESSAGE_BUFFER_TCB_SIZE_WORDS in FreeRTOS.h.");
4331
4332 const size_t block_size = stk::memory::BlockMemoryPool::AlignBlockSize(xMaxMessageSize);
4333 const size_t storage_size = xMessageCount * (block_size + FrtosMessageBuffer::ENVELOPE_SIZE);
4334
4335 FrtosMessageBuffer *mb = new (pxStaticMessageBuffer)
4336 FrtosMessageBuffer(xMaxMessageSize, xMessageCount,
4337 pucMessageBufferStorageArea, storage_size,
4338 pxSendCompletedCallback, pxReceiveCompletedCallback);
4339
4340 return static_cast<MessageBufferHandle_t>(mb);
4341}
4342
4344{
4345 if (xMessageBuffer == nullptr)
4346 return;
4347
4348 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4349
4350 ObjFree(mb);
4351}
4352
4354 const void *pvTxData,
4355 size_t xDataLengthBytes,
4356 TickType_t xTicksToWait)
4357{
4358 if ((xMessageBuffer == nullptr) || (pvTxData == nullptr) || (xDataLengthBytes == 0U))
4359 return 0U;
4360
4361 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4362
4363 if (xDataLengthBytes > mb->m_max_msg_size)
4364 {
4365 STK_ASSERT(false); // API contract: message too large for this buffer
4366 return 0U;
4367 }
4368
4369 const stk::Timeout stk_timeout = FrtosTimeoutToStk(xTicksToWait);
4370
4371 // 1. Acquire a payload block (blocks until one is free or timeout).
4372 void *blk = mb->m_pool.TimedAlloc(stk_timeout);
4373 if (blk == nullptr)
4374 return 0U;
4375
4376 // 2. Copy payload into the block.
4377 STK_MEMCPY(blk, pvTxData, xDataLengthBytes);
4378
4379 // 3. Enqueue the envelope (should always succeed: pool and eq are 1:1,
4380 // but guard with NO_WAIT to avoid deadlock if they desync).
4381 FrtosMessageBuffer::MsgEnvelope env = { xDataLengthBytes, blk };
4382
4383 if (!mb->m_eq.Put(&env, stk::NO_WAIT))
4384 {
4385 // Envelope queue unexpectedly full — return block and report failure.
4386 STK_ASSERT(false);
4387 mb->m_pool.Free(blk);
4388 return 0U;
4389 }
4390
4391 // Fire send-complete callback outside any critical section.
4392 if (mb->m_send_cb != nullptr)
4393 {
4394 BaseType_t woken = pdFALSE;
4395 mb->m_send_cb(static_cast<StreamBufferHandle_t>(xMessageBuffer), &woken);
4396 }
4397
4398 return xDataLengthBytes;
4399}
4400
4402 void *pvRxData,
4403 size_t xBufferLengthBytes,
4404 TickType_t xTicksToWait)
4405{
4406 if ((xMessageBuffer == nullptr) || (pvRxData == nullptr) || (xBufferLengthBytes == 0U))
4407 return 0U;
4408
4409 if (IsIrqContext() && (xTicksToWait != 0U))
4410 return 0U;
4411
4412 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4413
4414 // 1. Dequeue the next envelope.
4415 FrtosMessageBuffer::MsgEnvelope env = { 0U, nullptr };
4416
4417 if (!mb->m_eq.Get(&env, FrtosTimeoutToStk(xTicksToWait)))
4418 return 0U;
4419
4420 // 2. Validate destination capacity.
4421 if (xBufferLengthBytes < env.len)
4422 {
4423 // Destination too small: re-enqueue the envelope so the message is
4424 // not lost (best-effort; if re-enqueue also fails the message is lost).
4425 STK_ASSERT(false); // API contract: pvRxData buffer too small
4426 mb->m_eq.TryPut(&env);
4427 return 0U;
4428 }
4429
4430 // 3. Copy payload out and return the block.
4431 STK_MEMCPY(pvRxData, env.blk, env.len);
4432 mb->m_pool.Free(env.blk);
4433
4434 // Fire receive-complete callback outside any critical section.
4435 if (mb->m_recv_cb != nullptr)
4436 {
4437 BaseType_t woken = pdFALSE;
4438 mb->m_recv_cb(static_cast<StreamBufferHandle_t>(xMessageBuffer), &woken);
4439 }
4440
4441 return env.len;
4442}
4443
4445{
4446 if (xMessageBuffer == nullptr)
4447 return pdTRUE;
4448
4449 return static_cast<FrtosMessageBuffer *>(xMessageBuffer)->m_eq.IsEmpty()
4450 ? pdTRUE : pdFALSE;
4451}
4452
4454{
4455 if (xMessageBuffer == nullptr)
4456 return pdTRUE;
4457
4458 return static_cast<FrtosMessageBuffer *>(xMessageBuffer)->m_pool.IsFull()
4459 ? pdTRUE : pdFALSE;
4460}
4461
4463{
4464 if (xMessageBuffer == nullptr)
4465 return 0U;
4466
4467 // Free pool blocks == available envelope slots (they are 1:1).
4468 return static_cast<FrtosMessageBuffer *>(xMessageBuffer)->m_pool.GetFreeCount();
4469}
4470
4472{
4473 if (xMessageBuffer == nullptr)
4474 return pdFAIL;
4475
4476 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4477
4478 // Drain all pending envelopes and return their blocks to the pool.
4479 FrtosMessageBuffer::MsgEnvelope env = { 0U, nullptr };
4480
4481 while (mb->m_eq.TryGet(&env))
4482 {
4483 if (env.blk != nullptr)
4484 mb->m_pool.Free(env.blk);
4485 }
4486
4487 // Reset the envelope queue (wakes any blocked senders).
4488 mb->m_eq.Reset();
4489
4490 return pdPASS;
4491}
4492
4493// ===========================================================================
4494// Message Buffer ISR / extended API
4495//
4496// xMessageBufferSendFromISR
4497// -------------------------
4498// Mirrors xMessageBufferSend() but is strictly non-blocking:
4499// 1. TimedAlloc(NO_WAIT) — grabs a pool block without blocking.
4500// BlockMemoryPool::TimedAlloc() with NO_WAIT uses a ScopedCriticalSection
4501// internally, making it ISR-safe on all STK targets.
4502// 2. memcpy payload into the block.
4503// 3. TryPut (= Put(NO_WAIT)) — enqueues the envelope without blocking.
4504// MessageQueue::Put(NO_WAIT) is also ScopedCriticalSection-guarded.
4505//
4506// If step 1 fails (pool empty) we return 0 immediately — no block was
4507// consumed so there is nothing to free. If step 3 fails (should not happen
4508// given pool/eq are 1:1) we release the block and return 0, matching the
4509// defensive pattern in xMessageBufferSend().
4510//
4511// pxHigherPriorityTaskWoken is always set to pdFALSE: STK's condition
4512// variable machinery reschedules waiting tasks via the kernel's own
4513// priority mechanism without requiring the ISR to request a context switch.
4514//
4515// xMessageBufferReceiveFromISR
4516// ----------------------------
4517// Uses TryGet (= Get(NO_WAIT)) on m_eq, which is ISR-safe. On success the
4518// envelope length is validated against the caller's buffer, the payload is
4519// copied, and the pool block is freed (Pool::Free() is also CS-guarded).
4520// On buffer-too-small the envelope is put back at the front so the message
4521// is not lost — TryPutFront (= PutFront(NO_WAIT)) retreats the tail pointer
4522// under the same CS, restoring the queue state exactly.
4523//
4524// xMessageBufferResetFromISR
4525// --------------------------
4526// Identical logic to xMessageBufferReset(); all underlying operations
4527// (TryGet, Pool::Free, MessageQueue::Reset) are already ISR-safe.
4528// pxHigherPriorityTaskWoken is always pdFALSE for the same reason as above.
4529//
4530// xMessageBufferNextLengthBytes
4531// -----------------------------
4532// Implements a non-destructive peek of the oldest envelope's length field
4533// using TryPeek(), which copies the envelope atomically without consuming it.
4534// No manual Get + PutFront pair or outer ScopedCriticalSection is needed:
4535// TryPeek() acquires its own internal CS and leaves m_tail and m_count
4536// unchanged. Returns 0 if the buffer is empty.
4537// ===========================================================================
4538
4540 const void *pvTxData,
4541 size_t xDataLengthBytes,
4542 BaseType_t *pxHigherPriorityTaskWoken)
4543{
4544 if (pxHigherPriorityTaskWoken != nullptr)
4545 *pxHigherPriorityTaskWoken = pdFALSE;
4546
4547 if ((xMessageBuffer == nullptr) || (pvTxData == nullptr) || (xDataLengthBytes == 0U))
4548 return 0U;
4549
4550 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4551
4552 if (xDataLengthBytes > mb->m_max_msg_size)
4553 {
4554 STK_ASSERT(false); // API contract: message exceeds max size
4555 return 0U;
4556 }
4557
4558 // Step 1: non-blocking pool allocation.
4559 void *blk = mb->m_pool.TimedAlloc(stk::NO_WAIT);
4560 if (blk == nullptr)
4561 return 0U; // pool full — no block consumed, nothing to free
4562
4563 // Step 2: copy payload.
4564 STK_MEMCPY(blk, pvTxData, xDataLengthBytes);
4565
4566 // Step 3: non-blocking enqueue.
4567 FrtosMessageBuffer::MsgEnvelope env = { xDataLengthBytes, blk };
4568 if (!mb->m_eq.TryPut(&env))
4569 {
4570 // Pool and eq are 1:1, so this should never happen. Defensively free
4571 // the block to avoid a pool leak and report failure.
4572 STK_ASSERT(false);
4573 mb->m_pool.Free(blk);
4574 return 0U;
4575 }
4576
4577 // Fire send-complete callback outside any critical section.
4578 // pxHigherPriorityTaskWoken is always pdFALSE per STK convention.
4579 if (mb->m_send_cb != nullptr)
4580 {
4581 BaseType_t woken = pdFALSE;
4582 mb->m_send_cb(static_cast<StreamBufferHandle_t>(xMessageBuffer), &woken);
4583 }
4584
4585 return xDataLengthBytes;
4586}
4587
4589 void *pvRxData,
4590 size_t xBufferLengthBytes,
4591 BaseType_t *pxHigherPriorityTaskWoken)
4592{
4593 if (pxHigherPriorityTaskWoken != nullptr)
4594 *pxHigherPriorityTaskWoken = pdFALSE;
4595
4596 if ((xMessageBuffer == nullptr) || (pvRxData == nullptr) || (xBufferLengthBytes == 0U))
4597 return 0U;
4598
4599 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4600
4601 // Step 1: non-blocking dequeue of the oldest envelope.
4602 FrtosMessageBuffer::MsgEnvelope env = { 0U, nullptr };
4603 if (!mb->m_eq.TryGet(&env))
4604 return 0U; // buffer empty
4605
4606 // Step 2: validate destination capacity.
4607 if (xBufferLengthBytes < env.len)
4608 {
4609 // Destination too small: put the envelope back at the front so the
4610 // message is not lost. TryPutFront retreats the tail pointer under
4611 // its own CS — identical to Get never having happened.
4612 STK_ASSERT(false); // API contract: pvRxData buffer too small
4613 mb->m_eq.TryPutFront(&env);
4614 return 0U;
4615 }
4616
4617 // Step 3: copy payload out and release pool block.
4618 STK_MEMCPY(pvRxData, env.blk, env.len);
4619 mb->m_pool.Free(env.blk);
4620
4621 // Fire receive-complete callback outside any critical section.
4622 // pxHigherPriorityTaskWoken is always pdFALSE per STK convention.
4623 if (mb->m_recv_cb != nullptr)
4624 {
4625 BaseType_t woken = pdFALSE;
4626 mb->m_recv_cb(static_cast<StreamBufferHandle_t>(xMessageBuffer), &woken);
4627 }
4628
4629 return env.len;
4630}
4631
4633 BaseType_t *pxHigherPriorityTaskWoken)
4634{
4635 if (pxHigherPriorityTaskWoken != nullptr)
4636 *pxHigherPriorityTaskWoken = pdFALSE;
4637
4638 if (xMessageBuffer == nullptr)
4639 return pdFAIL;
4640
4641 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4642
4643 // Drain all pending envelopes, returning every pool block before resetting
4644 // the queue. All three operations are ISR-safe (ScopedCriticalSection).
4645 FrtosMessageBuffer::MsgEnvelope env = { 0U, nullptr };
4646
4647 while (mb->m_eq.TryGet(&env))
4648 {
4649 if (env.blk != nullptr)
4650 mb->m_pool.Free(env.blk);
4651 }
4652
4653 // Reset the envelope queue; wakes any sender blocked on a full pool.
4654 mb->m_eq.Reset();
4655
4656 return pdPASS;
4657}
4658
4660{
4661 if (xMessageBuffer == nullptr)
4662 return 0U;
4663
4664 FrtosMessageBuffer *mb = static_cast<FrtosMessageBuffer *>(xMessageBuffer);
4665
4666 // TryPeek copies the oldest envelope without consuming it. The internal
4667 // ScopedCriticalSection makes this ISR-safe; no outer CS is required.
4668 FrtosMessageBuffer::MsgEnvelope env = { 0U, nullptr };
4669
4670 if (!mb->m_eq.TryPeek(&env))
4671 return 0U; // buffer is empty — nothing to peek at
4672
4673 return env.len;
4674}
4675
4676#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 void STK_MEMCPY(void *const dest, const void *const src, const size_t size)
Implementation of STK_MEMCPY.
Definition stk_arch.h:731
#define STK_UNUSED(X)
Explicitly marks a variable as unused to suppress compiler warnings.
Definition stk_defs.h:715
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:516
#define __stk_weak
Marks a function or variable as weak, allowing it to be overridden by the user.
Definition stk_defs.h:300
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 SemaphoreHandle_t CreateMutexInternal(bool recursive)
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 SemaphoreHandle_t CreateMutexStaticInternal(StaticSemaphore_t *pxMutexBuffer, bool recursive)
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::FastMutex or 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:160
static void Yield()
Notify scheduler to switch to the next runnable task.
Definition stk_helper.h:560
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:497
static void SleepCancel(TId task_id)
Cancel sleep of the task.
Definition stk_helper.h:538
EAccessMode
Hardware access modes by the task.
Definition stk_common.h:36
@ ACCESS_PRIVILEGED
Privileged access mode (access to hardware is fully unrestricted).
Definition stk_common.h:38
static constexpr Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:234
int64_t Ticks
Ticks value.
Definition stk_common.h:175
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:170
static bool SleepUntil(Ticks timestamp)
Put calling process into a sleep state until the specified timestamp.
Definition stk_helper.h:522
static Ticks GetTicks()
Get number of ticks elapsed since kernel start.
Definition stk_helper.h:452
PlatformArmCortexM PlatformDefault
Default platform implementation.
static constexpr Timeout WAIT_INFINITE
Timeout value: block indefinitely until the synchronization object is signaled.
Definition stk_common.h:228
static TId GetTid()
Get task/thread Id of the calling task.
Definition stk_helper.h:376
static constexpr TId TID_NONE
Reserved task/thread id representing zero/none thread id.
Definition stk_common.h:222
Word TId
Task (thread) id.
Definition stk_common.h:165
@ KERNEL_TICKLESS
Tickless mode. To use this mode STK_TICKLESS_IDLE must be defined to 1 in stk_config....
Definition stk_common.h:51
@ KERNEL_SYNC
Synchronization support (see Event).
Definition stk_common.h:50
@ KERNEL_DYNAMIC
Tasks can be added or removed and therefore exit when done.
Definition stk_common.h:48
bool IsInsideISR()
Check whether the CPU is currently executing inside a hardware interrupt service routine (ISR).
static void * Allocate(size_t size) __stk_weak
Allocate the memory chunk.
static void Free(void *ptr) __stk_weak
Free 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:100
static Session Enter(const Session ses=DEFAULT_SESSION)
Enter a critical section.
static void Exit(const Session ses=DEFAULT_SESSION)
Exit a critical section.
RAII instance that enters the critical section on construction and exits it on destruction.
Definition stk_arch.h:494
virtual size_t GetStackSpace() const
Get available stack space.
Definition stk_common.h:465
Interface for a user task.
Definition stk_common.h:824
@ 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.
Non-recursive (binary) mutex primitive with ownership tracking.
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.
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 pointer to the stack memory.
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)
virtual stk::TId GetOwner() const =0
virtual ~FrtosMutexBase()
virtual bool Take(stk::Timeout timeout)=0
virtual void Give()=0
virtual bool IsRecursive() const =0
stk::sync::Mutex m_mutex
bool IsRecursive() const override
void Give() override
stk::TId GetOwner() const override
bool Take(stk::Timeout timeout) override
bool Take(stk::Timeout timeout) override
stk::TId GetOwner() const override
void Give() override
bool IsRecursive() const override
stk::sync::FastMutex m_mutex
FrtosMutexBase * m_mtx
stk::sync::Semaphore * m_sem
FrtosQueueSet * m_set
non-owning ptr to the queue set this member belongs to (nullptr if none)
FrtosSemaphore(SemKind kind, uint16_t initial, uint16_t max_count, bool recursive=false)
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