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