F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
PriorityMemQueue.cpp
Go to the documentation of this file.
1 // ======================================================================
2 // \title Os/Generic/PriorityMemQueue.cpp
3 // \author B. Duckett
4 // \brief cpp file for AtomicQueue-based priority queue implementation for Os::Queue
5 //
6 // \copyright
7 // Copyright 2026, by the California Institute of Technology.
8 // ALL RIGHTS RESERVED. United States Government Sponsorship
9 // acknowledged.
10 // ======================================================================
11 #include "PriorityMemQueue.hpp"
12 #include <algorithm>
13 #include <cstdio>
14 #include <cstring>
15 #include <limits>
16 #include "Fw/LanguageHelpers.hpp"
17 #include "Fw/Types/Assert.hpp"
20 
21 namespace Os {
22 namespace Generic {
23 
24 // Arbitrary (but small) limit to prevent infinite loops
25 // Don't expect a message queue depth to ever exceed three digits
26 constexpr U32 LOOP_GUARD_LIMIT = 2000;
27 
28 // ======================================================================
29 // Static Configuration State (Global)
30 // ======================================================================
31 // THREAD-SAFETY: Static configuration is designed for single-threaded
32 // initialization at system startup (before any queues are created).
33 // configure() asserts if called multiple times. Individual queue
34 // creation uses atomic s_configsUsed[] to prevent race conditions when
35 // multiple components instantiate queues concurrently.
36 //
37 // LIFECYCLE:
38 // 1. System startup: Call configure() once (single-threaded)
39 // 2. Component init: create() claims config atomically (multi-threaded safe)
40 // 3. Runtime: No modification to static state
41 // 4. Teardown: Each queue marks config unused atomically
42 // 5. Test only: resetConfig() deallocates (single-threaded after all queues destroyed)
43 // ======================================================================
44 PriorityMemQueue::QueueConfig* PriorityMemQueue::s_configs = nullptr;
45 FwSizeType PriorityMemQueue::s_numConfigs = 0;
46 bool PriorityMemQueue::s_requirePrioritySizing = false;
47 std::atomic<bool>* PriorityMemQueue::s_configsUsed = nullptr;
48 bool PriorityMemQueue::s_configured = false;
49 FwEnumStoreType PriorityMemQueue::s_allocatorId = 0;
50 
54 static constexpr U32 priorityBitMask(FwQueuePriorityType priority) {
55  return 1U << priority;
56 }
57 
60  static_cast<FwAssertArgType>(this->m_numActivePriorities));
61  // NOTE: Do NOT reset m_priorityMap here - it's already populated by create()
62 
63  // If arrays were allocated, teardown AtomicQueues
64  if (this->m_atomicQueues != nullptr) {
65  for (FwSizeType i = 0; i < this->m_numActivePriorities; ++i) {
66  this->m_atomicQueues[i].teardown();
67  }
68  }
69 
70  // Initialize high water marks to zero if array is allocated
71  if (this->m_highWaterMarks != nullptr) {
72  for (FwSizeType i = 0; i < this->m_numActivePriorities; ++i) {
73  this->m_highWaterMarks[i].store(0, std::memory_order_relaxed);
74  }
75  }
76 
77  // Initialize the not-empty semaphore with count 0 (queue starts empty)
78  if (this->m_notEmptySem != nullptr) {
80  this->m_notEmptySem = nullptr;
81  }
82 
83  // Initialize atomic variables
84  this->m_priorityMask.store(1U << Os::Generic::Queue::DEFAULT_PRIORITY, std::memory_order_relaxed);
85 }
86 
88  this->m_allocatorId = allocatorId;
89 
90  // Scan m_priorityMap to count configured priorities and find max priority
91  this->m_numActivePriorities = 0;
92  this->m_maxPriority = 0;
93 
94  for (FwSizeType p = 0; p < Os::Generic::Queue::MAX_PRIORITIES; ++p) {
95  if (this->m_priorityMap[p] >= 0) {
96  this->m_numActivePriorities++;
97  if (static_cast<FwQueuePriorityType>(p) > this->m_maxPriority) {
98  this->m_maxPriority = static_cast<FwQueuePriorityType>(p);
99  }
100  }
101  }
102 
103  FW_ASSERT(this->m_numActivePriorities > 0, allocatorId);
104 
105  // Allocate memory for atomicQueues array (sized to actual configured priorities)
106  FwSizeType atomicQueuesSize = sizeof(Types::AtomicQueue) * this->m_numActivePriorities;
107  void* atomicQueuesMem = allocator.checkedAllocate(allocatorId, atomicQueuesSize, alignof(Types::AtomicQueue));
108  if (atomicQueuesMem == nullptr) {
109  this->deallocateArrays(allocator, allocatorId);
110  return false;
111  }
112  // Use placement new to construct array
113  this->m_atomicQueues = static_cast<Types::AtomicQueue*>(atomicQueuesMem);
114  for (FwSizeType i = 0; i < this->m_numActivePriorities; ++i) {
115  new (&this->m_atomicQueues[i]) Types::AtomicQueue();
116  }
117 
118  // Allocate memory for highWaterMarks array (sized to actual configured priorities)
119  FwSizeType hwmSize = sizeof(std::atomic<U32>) * this->m_numActivePriorities;
120  void* hwmMem = allocator.checkedAllocate(allocatorId, hwmSize, alignof(std::atomic<U32>));
121  if (hwmMem == nullptr) {
122  this->deallocateArrays(allocator, allocatorId);
123  return false;
124  }
125  // Cast required: MemAllocator returns void*, reinterpret_cast converts to typed pointer for placement new
126  this->m_highWaterMarks = reinterpret_cast<std::atomic<U32>*>(hwmMem);
127  for (FwSizeType i = 0; i < this->m_numActivePriorities; ++i) {
128  // Placement new constructs std::atomic in pre-allocated memory (standard F' allocator pattern)
129  new (&this->m_highWaterMarks[i]) std::atomic<U32>(0);
130  }
131 
132  return true;
133 }
134 
137  static_cast<FwAssertArgType>(this->m_numActivePriorities));
138  // Deallocate arrays in reverse order
139  if (this->m_highWaterMarks != nullptr) {
140  // std::atomic<U32> is trivially destructible — no explicit destructor needed
141  allocator.deallocate(allocatorId, this->m_highWaterMarks);
142  this->m_highWaterMarks = nullptr;
143  }
144  if (this->m_atomicQueues != nullptr) {
145  // Explicitly destroy AtomicQueues before deallocation
146  for (FwSizeType i = 0; i < this->m_numActivePriorities; ++i) {
147  this->m_atomicQueues[i].~AtomicQueue();
148  }
149  allocator.deallocate(allocatorId, this->m_atomicQueues);
150  this->m_atomicQueues = nullptr;
151  }
152 
153  // Reset priority map
154  for (FwSizeType i = 0; i < Os::Generic::Queue::MAX_PRIORITIES; ++i) {
155  this->m_priorityMap[i] = -1;
156  }
157 
158  this->m_maxPriority = 0;
159  this->m_numActivePriorities = 0;
160  this->m_allocatorId = 0;
161 }
162 
164  FW_ASSERT(priority < Os::Generic::Queue::MAX_PRIORITIES, priority, this->m_id);
165  // Enabling a priority is only allowed if it's in use
166  FW_ASSERT(this->m_atomicQueues != nullptr, this->m_id, priority);
167 
168  // MEMORY ORDERING: seq_cst for control path operations ensures total ordering
169  // Atomic update of priority mask using fetch_or with seq_cst (control path)
170  (void)this->m_priorityMask.fetch_or(priorityBitMask(priority), std::memory_order_seq_cst);
171 }
172 
174  FW_ASSERT(priority < Os::Generic::Queue::MAX_PRIORITIES, this->m_id);
175 
176  // MEMORY ORDERING: seq_cst for control path operations ensures total ordering
177  // Atomic update of priority mask using fetch_and with seq_cst (control path)
178  (void)this->m_priorityMask.fetch_and(~priorityBitMask(priority), std::memory_order_seq_cst);
179 }
180 
182  // Initialize handle to safe defaults
183  this->m_handle.init();
184 }
185 
189 static inline I32 findMSB(U32 value) {
190  if (value == 0) {
191  return -1;
192  }
193  // Use compiler builtin for CLZ (count leading zeros) if available
194 #if defined(__GNUC__) || defined(__clang__)
195  I32 msb = 31 - __builtin_clz(value);
196  return msb;
197 #else
198  // Fallback: software implementation with explicit bound (32 bits maximum)
199  I32 msb = 31;
200  U32 mask = 0x80000000;
201  for (FwSizeType bit = 0; bit < 32; ++bit) {
202  if (mask & value) {
203  return msb;
204  }
205  --msb;
206  mask >>= 1;
207  }
208  return -1;
209 #endif
210 }
211 
212 FwQueuePriorityType PriorityMemQueue::findHighestPriority(U32 priorities) {
213  // The priority bit mask is a U32, so priorities must fit in 32 bits
214  FW_ASSERT(static_cast<FwSizeType>(Os::Generic::Queue::MAX_PRIORITIES) <= 32);
215  // MEMORY ORDERING: Use acquire to synchronize with priority enable/disable operations
216  // Get enabled priorities
217  if (priorities == 0) {
218  priorities = this->m_handle.m_priorityMask.load(std::memory_order_acquire);
219  }
220 
221  if (priorities == 0) {
223  }
224 
225  // Use IPC-style MSB finding for performance
226  I32 msb = findMSB(priorities);
227  if (msb < 0 || msb >= static_cast<I32>(Os::Generic::Queue::MAX_PRIORITIES)) {
229  }
230  return static_cast<FwQueuePriorityType>(msb);
231 }
232 
233 bool PriorityMemQueue::isPriorityEnabled(FwQueuePriorityType priority) {
234  FW_ASSERT(priority < Os::Generic::Queue::MAX_PRIORITIES, this->m_handle.m_id);
235  // MEMORY ORDERING: Use acquire to synchronize with enable/disable operations
236  return (this->m_handle.m_priorityMask.load(std::memory_order_acquire) & priorityBitMask(priority)) != 0;
237 }
238 
239 void PriorityMemQueue::setPriorityEnabled(FwQueuePriorityType priority, bool enabled) {
240  // Delegate to handle methods (SSOT)
241  if (enabled) {
242  this->m_handle.enablePriority(priority);
243  } else {
244  this->m_handle.disablePriority(priority);
245  }
246 }
247 
248 Fw::MemAllocator& PriorityMemQueue::getAllocator() {
251 }
252 
256 static void validateQueueConfigs(PriorityMemQueue::QueueConfig* queueConfigs, FwSizeType numQueueConfigs) {
257  for (FwSizeType i = 0; i < numQueueConfigs; ++i) {
258  PriorityMemQueue::QueueConfig* currentConfig = &queueConfigs[i];
259 
260  // Assert if numPriorities is 0 or exceeds maximum
261  FW_ASSERT(
262  currentConfig->numPriorities > 0 && currentConfig->numPriorities <= Os::Generic::Queue::MAX_PRIORITIES,
263  static_cast<FwAssertArgType>(i), currentConfig->instanceId,
264  static_cast<FwAssertArgType>(currentConfig->numPriorities));
265 
266  // Check for duplicate instance IDs
267  for (FwSizeType j = i + 1; j < numQueueConfigs; ++j) {
268  PriorityMemQueue::QueueConfig* otherConfig = &queueConfigs[j];
269  FW_ASSERT(currentConfig->instanceId != otherConfig->instanceId, currentConfig->instanceId,
270  static_cast<FwAssertArgType>(i), static_cast<FwAssertArgType>(j));
271  }
272 
273  // Check priority configurations
274  PriorityMemQueue::QueuePriorityConfig* priorityConfigs = currentConfig->priorityConfigs;
275  FW_ASSERT(priorityConfigs != nullptr, static_cast<FwAssertArgType>(i), currentConfig->instanceId,
276  static_cast<FwAssertArgType>(currentConfig->numPriorities));
277  for (FwSizeType p = 0; p < currentConfig->numPriorities; ++p) {
278  PriorityMemQueue::QueuePriorityConfig* pConfig = &priorityConfigs[p];
279 
280  // Assert if maxMsgSize or numMsgs is 0
281  FW_ASSERT(pConfig->maxMsgSize > 0, static_cast<FwAssertArgType>(i), static_cast<FwAssertArgType>(p),
282  pConfig->priority);
283  FW_ASSERT(pConfig->numMsgs > 0, static_cast<FwAssertArgType>(i), static_cast<FwAssertArgType>(p),
284  pConfig->priority);
286  static_cast<FwAssertArgType>(i), static_cast<FwAssertArgType>(p), pConfig->priority);
287 
288  // Check for duplicate priority values
289  for (FwSizeType q = p + 1; q < currentConfig->numPriorities; ++q) {
290  PriorityMemQueue::QueuePriorityConfig* qConfig = &priorityConfigs[q];
291  FW_ASSERT(pConfig->priority != qConfig->priority, static_cast<FwAssertArgType>(i),
292  currentConfig->instanceId, pConfig->priority);
293  }
294  }
295  }
296 }
297 
299  FwSizeType numQueueConfigs,
300  bool required,
301  FwEnumStoreType allocatorId) {
302  // Accept NULL pointer if and only if numQueueConfigs is 0
303  FW_ASSERT((queueConfigs != nullptr) || (numQueueConfigs == 0), 0);
304 
305  // Assert if already configured - that's not a supported use case
306  FW_ASSERT(!s_configured, 0);
307 
308  s_configured = true;
309 
310  // Assert if required is true but num priorities is 0
311  FW_ASSERT(!(required && numQueueConfigs == 0), required, static_cast<FwAssertArgType>(numQueueConfigs));
312 
313  // Validate all configurations
314  if (queueConfigs != nullptr) {
315  validateQueueConfigs(queueConfigs, numQueueConfigs);
316  }
317  // Get the memory allocator configured for priority queues
320  // Allocate memory for tracking used configurations
321  if (numQueueConfigs > 0) {
322  FwSizeType expSize = numQueueConfigs * sizeof(std::atomic<bool>);
323  s_configsUsed = static_cast<std::atomic<bool>*>(
324  allocator.checkedAllocate(allocatorId, expSize, alignof(std::atomic<bool>)));
325  FW_ASSERT(s_configsUsed != nullptr);
326  // Initialize all entries to false using placement new
327  for (FwSizeType i = 0; i < numQueueConfigs; ++i) {
328  new (&s_configsUsed[i]) std::atomic<bool>(false);
329  }
330  }
331 
332  // Deep copy into a single contiguous block: QueueConfig[] followed by all QueuePriorityConfig[] sub-arrays.
333  static_assert(sizeof(QueueConfig) % alignof(QueuePriorityConfig) == 0,
334  "QueueConfig array must be naturally aligned with QueuePriorityConfig");
335  if (numQueueConfigs > 0) {
336  FwSizeType totalSize = numQueueConfigs * sizeof(QueueConfig);
337  for (FwSizeType i = 0; i < numQueueConfigs; ++i) {
338  totalSize += queueConfigs[i].numPriorities * sizeof(QueuePriorityConfig);
339  }
340  void* configsMem = allocator.checkedAllocate(allocatorId, totalSize, alignof(QueueConfig));
341  FW_ASSERT(configsMem != nullptr);
342  s_configs = static_cast<QueueConfig*>(configsMem);
343  QueuePriorityConfig* priorityBase = reinterpret_cast<QueuePriorityConfig*>(s_configs + numQueueConfigs);
344  for (FwSizeType i = 0; i < numQueueConfigs; ++i) {
345  s_configs[i] = queueConfigs[i];
346  FwSizeType priorityConfigsSize = queueConfigs[i].numPriorities * sizeof(QueuePriorityConfig);
347  (void)memcpy(priorityBase, queueConfigs[i].priorityConfigs, priorityConfigsSize);
348  s_configs[i].priorityConfigs = priorityBase;
349  priorityBase += queueConfigs[i].numPriorities;
350  }
351  }
352  s_numConfigs = numQueueConfigs;
353  s_requirePrioritySizing = required;
354  s_allocatorId = allocatorId;
355 }
356 
358  // Configs are allocated if and only if a nonzero count was configured
359  FW_ASSERT((s_configs != nullptr) || (s_numConfigs == 0), static_cast<FwAssertArgType>(s_numConfigs));
360  // Only call this in test environments after all queues are destroyed
361  if (s_configsUsed != nullptr || s_configs != nullptr) {
362  // Get allocator (same as used in config())
365  FwEnumStoreType allocatorId = s_allocatorId;
366 
367  // Deallocate the tracking array
368  if (s_configsUsed != nullptr) {
369  allocator.deallocate(allocatorId, s_configsUsed);
370  s_configsUsed = nullptr;
371  }
372 
373  // Deallocate the single contiguous config block (QueueConfig[] + all QueuePriorityConfig[] sub-arrays)
374  if (s_configs != nullptr) {
375  allocator.deallocate(allocatorId, s_configs);
376  }
377  }
378 
379  // Reset all static state
380  s_configs = nullptr;
381  s_numConfigs = 0;
382  s_requirePrioritySizing = false;
383  s_configured = false;
384 }
385 
387  this->teardownInternal();
388 }
389 
391  const Fw::ConstStringBase& name,
392  FwSizeType depth,
393  FwSizeType messageSize) {
394  FW_ASSERT(depth > 0, id);
395  FW_ASSERT(messageSize > 0, id);
396 
397  // Initialize the handle ID
398  this->m_handle.m_id = id;
399 
400  // Get the memory allocator for queue operations
401  Fw::MemAllocator& allocator = this->getAllocator();
402 
403  // Find a matching configuration if one exists
404  QueueConfig* queueConfig = findMatchingConfig(id);
405 
406  // Build priority map from configuration
407  if (queueConfig != nullptr) {
408  // Map each configured priority to array index
409  for (FwSizeType i = 0; i < queueConfig->numPriorities; ++i) {
410  FwQueuePriorityType p = queueConfig->priorityConfigs[i].priority;
411  FW_ASSERT(p < Os::Generic::Queue::MAX_PRIORITIES, id, static_cast<FwAssertArgType>(p),
413  this->m_handle.m_priorityMap[p] = static_cast<I8>(i);
414  }
415  } else {
416  // Default: single priority at DEFAULT_PRIORITY
418  }
419 
420  // Allocate arrays for priority data (reads from m_priorityMap)
421  if (!this->m_handle.allocateArrays(allocator, id)) {
422  return Os::QueueInterface::Status::ALLOCATION_FAILED;
423  }
424 
425  // Initialize the handle with allocated arrays
426  this->m_handle.init();
427 
428  // Allocate and create the not-empty semaphore (initial count 0)
429  FwSizeType semSize = sizeof(Os::CountingSemaphore);
430  void* semMem = allocator.checkedAllocate(id, semSize, alignof(Os::CountingSemaphore));
431  if (semMem == nullptr) {
432  this->m_handle.deallocateArrays(allocator, id);
433  return Os::QueueInterface::Status::ALLOCATION_FAILED;
434  }
435  this->m_handle.m_notEmptySem = new (semMem) Os::CountingSemaphore(static_cast<U32>(0));
436 
437  // Create the priority queues based on configuration
438  if (queueConfig != nullptr) {
439  // Use the found configuration to create multiple priority queues
440  return createConfiguredQueues(queueConfig, allocator, id);
441  } else {
442  // No configuration found, create a single default priority queue
443  return createDefaultQueue(depth, messageSize, allocator, id);
444  }
445 }
446 
447 // Helper method to find a matching configuration for the given ID
448 PriorityMemQueue::QueueConfig* PriorityMemQueue::findMatchingConfig(FwEnumStoreType id) {
449  if (s_configs != nullptr && s_configsUsed != nullptr) {
450  for (FwSizeType i = 0; i < s_numConfigs; ++i) {
451  if (s_configs[i].instanceId == id) {
452  // Atomic check-and-set to claim configuration
453  bool expected = false;
454  if (s_configsUsed[i].compare_exchange_strong(expected, true, std::memory_order_acq_rel)) {
455  return &s_configs[i];
456  } else {
457  // Configuration already in use, assert failure
458  FW_ASSERT(false, id, s_configs[i].instanceId);
459  }
460  }
461  }
462  }
463  return nullptr;
464 }
465 
466 // Helper method to create queues based on configuration
467 QueueInterface::Status PriorityMemQueue::createConfiguredQueues(QueueConfig* queueConfig,
468  Fw::MemAllocator& allocator,
469  FwEnumStoreType allocatorId) {
470  FW_ASSERT(queueConfig != nullptr, this->m_handle.m_id);
471  FW_ASSERT(queueConfig->priorityConfigs != nullptr, this->m_handle.m_id,
472  static_cast<FwAssertArgType>(queueConfig->numPriorities));
473 
474  for (FwSizeType i = 0; i < queueConfig->numPriorities; ++i) {
475  const QueuePriorityConfig& priorityConfig = queueConfig->priorityConfigs[i];
476  FwQueuePriorityType priority = priorityConfig.priority;
477 
478  // Create and initialize the priority queue
479  QueueInterface::Status status = this->createPriorityQueue(priority, priorityConfig.maxMsgSize,
480  priorityConfig.numMsgs, allocator, allocatorId);
481 
482  if (status != Os::QueueInterface::Status::OP_OK) {
483  return status;
484  }
485 
486  // Enable this priority
487  this->setPriorityEnabled(priority, true);
488  }
489 
491 }
492 
493 // Helper method to create a single default priority queue
494 QueueInterface::Status PriorityMemQueue::createDefaultQueue(FwSizeType depth,
495  FwSizeType messageSize,
496  Fw::MemAllocator& allocator,
497  FwEnumStoreType allocatorId) {
498  // Create and initialize the default priority queue
499  return this->createPriorityQueue(Os::Generic::Queue::DEFAULT_PRIORITY, messageSize, depth, allocator, allocatorId);
500 }
501 
502 // Helper method to create a single priority queue using AtomicQueue
503 QueueInterface::Status PriorityMemQueue::createPriorityQueue(FwQueuePriorityType priority,
504  FwSizeType maxMsgSize,
505  FwSizeType numMsgs,
506  Fw::MemAllocator& allocator,
507  FwEnumStoreType allocatorId) {
508  FW_ASSERT(this->m_handle.m_atomicQueues != nullptr, this->m_handle.m_id, priority);
509  FW_ASSERT(priority < Os::Generic::Queue::MAX_PRIORITIES, this->m_handle.m_id, priority);
510  FW_ASSERT(priority <= this->m_handle.m_maxPriority, this->m_handle.m_id, priority, this->m_handle.m_maxPriority);
511 
512  // Get array index for this priority
513  I8 index = this->m_handle.getPriorityIndex(priority);
514  FW_ASSERT(index >= 0, this->m_handle.m_id, priority);
515 
516  // Get pointer to the AtomicQueue at mapped index (already constructed in allocateArrays)
517  Types::AtomicQueue* atomicQueue = &this->m_handle.m_atomicQueues[index];
518 
519  // Create the AtomicQueue with the specified parameters
520  atomicQueue->create(numMsgs, maxMsgSize, allocator, allocatorId);
521 
522  // Check if creation was successful (both capacity and slots must be initialized)
523  if (!atomicQueue->isCreated()) {
524  this->teardownInternal();
525  return Os::QueueInterface::Status::ALLOCATION_FAILED;
526  }
527 
528  // Enable this priority
529  this->setPriorityEnabled(priority, true);
530 
532 }
533 
535  this->teardownInternal();
536 }
537 
539  FW_ASSERT(this->m_handle.m_atomicQueues != nullptr || this->m_handle.m_maxPriority == 0, this->m_handle.m_id);
540 
541  // Teardown all AtomicQueues if arrays are allocated
542  if (this->m_handle.m_atomicQueues != nullptr) {
543  for (FwSizeType i = 0; i < this->m_handle.m_numActivePriorities; ++i) {
544  this->m_handle.m_atomicQueues[i].teardown();
545  }
546  }
547 
548  // Delete the not-empty semaphore
549  if (this->m_handle.m_notEmptySem != nullptr) {
550  Fw::MemAllocator& allocator = this->getAllocator();
551  FW_ASSERT(this->m_handle.m_allocatorId != 0 || this->m_handle.m_notEmptySem != nullptr, this->m_handle.m_id);
553  allocator.deallocate(this->m_handle.m_allocatorId, this->m_handle.m_notEmptySem);
554  this->m_handle.m_notEmptySem = nullptr;
555  }
556 
557  // Reset handle state
558  this->m_handle.m_priorityMask.store(1U << Os::Generic::Queue::DEFAULT_PRIORITY, std::memory_order_relaxed);
559 
560  // Deallocate arrays using stored allocator ID
561  Fw::MemAllocator& allocator = this->getAllocator();
562  this->m_handle.deallocateArrays(allocator, this->m_handle.m_allocatorId);
563 
564  // If we were using a configuration, mark it as unused
565  FW_ASSERT(s_configs != nullptr || s_configsUsed == nullptr, this->m_handle.m_id);
566  FW_ASSERT(s_configsUsed != nullptr || s_configs == nullptr, this->m_handle.m_id);
567 
568  if (s_configs != nullptr && s_configsUsed != nullptr) {
569  for (FwSizeType i = 0; i < s_numConfigs; ++i) {
570  if (s_configs[i].instanceId == this->m_handle.m_id && s_configsUsed[i].load()) {
571  s_configsUsed[i].store(false);
572  break;
573  }
574  }
575  }
576 }
577 
585  FwQueuePriorityType& priority,
586  FwEnumStoreType queueId,
587  bool requirePrioritySizing) {
588  // Look up priority in sparse map
589  I8 index = handle.getPriorityIndex(priority);
590 
591  // If priority not configured, fall back to default
592  if (index < 0) {
593  if (requirePrioritySizing) {
594  FW_ASSERT(false, queueId, requirePrioritySizing, priority, handle.m_maxPriority);
595  }
597  index = handle.getPriorityIndex(priority);
598  FW_ASSERT(index >= 0, queueId, priority);
599  }
600 
601  // Get AtomicQueue at mapped index
602  FW_ASSERT(index < static_cast<I8>(handle.m_numActivePriorities), queueId, static_cast<FwAssertArgType>(index),
603  static_cast<FwAssertArgType>(handle.m_numActivePriorities));
604  Types::AtomicQueue* atomicQueue = &handle.m_atomicQueues[index];
605  FW_ASSERT(atomicQueue->isCreated(), queueId, priority);
606  return atomicQueue;
607 }
608 
614 static void updateHighWaterMark(std::atomic<U32>* highWaterMarks,
615  FwSizeType index,
616  U32 currentDepth,
617  FwEnumStoreType queueId) {
618  FW_ASSERT(highWaterMarks != nullptr, queueId, static_cast<FwAssertArgType>(index));
619  U32 prevMax = highWaterMarks[index].load(std::memory_order_acquire);
620  // This is best effort, debug data only. So if retry count is exceeded, just give up
621  constexpr U32 MAX_CAS_RETRIES = 100;
622  for (U32 casRetries = 0; casRetries < MAX_CAS_RETRIES; ++casRetries) {
623  if (currentDepth <= prevMax) {
624  return; // No update needed
625  }
626  if (highWaterMarks[index].compare_exchange_weak(prevMax, currentDepth, std::memory_order_release,
627  std::memory_order_acquire)) {
628  return; // Update succeeded
629  }
630  }
631 }
632 
634  FwSizeType size,
635  FwQueuePriorityType priority,
636  QueueInterface::BlockingType blockType) {
637  // Validate input parameters
638  FW_ASSERT(buffer != nullptr, this->m_handle.m_id, priority, blockType);
639  FW_ASSERT(size > 0, this->m_handle.m_id, static_cast<FwAssertArgType>(size), priority);
640 
641  // Check if priority is valid
642  if (priority >= Os::Generic::Queue::MAX_PRIORITIES) {
643  return QueueInterface::Status::INVALID_PRIORITY;
644  }
645 
646  // Check if the queue is initialized
647  if (this->m_handle.m_atomicQueues == nullptr) {
648  return QueueInterface::Status::UNINITIALIZED;
649  }
650 
651  // Resolve priority to valid queue
652  Types::AtomicQueue* atomicQueue =
653  resolvePriorityQueue(this->m_handle, priority, this->m_handle.m_id, s_requirePrioritySizing);
654 
655  // Check for sizing problem
656  if (size > atomicQueue->getBufferSize()) {
657  return QueueInterface::Status::SIZE_MISMATCH;
658  }
659 
660  // Send message using AtomicQueue
661  bool success;
662  if (blockType == QueueInterface::BlockingType::BLOCKING) {
663  success = atomicQueue->enqueueBlocking(buffer, size, true);
664  } else {
665  success = atomicQueue->enqueue(buffer, size);
666  }
667 
668  if (!success) {
669  return QueueInterface::Status::FULL;
670  }
671 
672  // Update per-priority high water mark (use array index, not priority value)
673  I8 index = this->m_handle.getPriorityIndex(priority);
674  FW_ASSERT(index >= 0, this->m_handle.m_id, priority);
675  U32 currentDepth = static_cast<U32>(atomicQueue->getSize());
676  updateHighWaterMark(this->m_handle.m_highWaterMarks, static_cast<FwSizeType>(index), currentDepth,
677  this->m_handle.m_id);
678 
679  // Post semaphore to wake up receiver (if any)
680  FW_ASSERT(this->m_handle.m_notEmptySem != nullptr, this->m_handle.m_id, priority);
682  FW_ASSERT(semStatus == Os::CountingSemaphoreInterface::Status::OP_OK, static_cast<FwAssertArgType>(semStatus));
683 
685 }
686 
688  FwSizeType capacity,
690  FwSizeType& actualSize,
691  FwQueuePriorityType& priority) {
692  // Validate input parameters
693  FW_ASSERT(destination != nullptr, blockType, this->m_handle.m_id);
694 
695  // Check if the queue is initialized
696  if (this->m_handle.m_atomicQueues == nullptr) {
697  return QueueInterface::Status::UNINITIALIZED;
698  }
699 
700  // Check if the blocking type is valid
701  FW_ASSERT(
702  blockType == QueueInterface::BlockingType::BLOCKING || blockType == QueueInterface::BlockingType::NONBLOCKING,
703  blockType, this->m_handle.m_id);
704 
705  // RECEIVE FLOW: Scan all enabled priorities from highest to lowest.
706  // For each enabled priority, use getSize() as a cheap pre-filter (2 relaxed loads).
707  // Attempt dequeue only when getSize() > 0; dequeue CAS is the authoritative check.
708  // If getSize() is transiently stale and dequeue() fails, continue to next priority.
709  // Liveness is guaranteed by the semaphore — spurious wakes just re-scan.
710  //
711  // MEMORY ORDERING: acquire on priority mask ensures visibility of queue state.
712 
713  // Bounded loop with compile-time limit
714  for (U32 reps = 0; reps < LOOP_GUARD_LIMIT; ++reps) {
715  U32 enabledPriorities = this->m_handle.m_priorityMask.load(std::memory_order_acquire);
716 
717  for (I32 p = this->m_handle.m_maxPriority; p >= 0; --p) {
718  FwQueuePriorityType testPriority = static_cast<FwQueuePriorityType>(p);
719  if ((enabledPriorities & priorityBitMask(testPriority)) == 0) {
720  continue;
721  }
722  // Look up priority in sparse map
723  I8 index = this->m_handle.getPriorityIndex(testPriority);
724  FW_ASSERT(index >= 0, index, testPriority, this->m_handle.m_id);
725  FW_ASSERT(this->m_handle.m_atomicQueues != nullptr, this->m_handle.m_id, testPriority);
726  Types::AtomicQueue* aq = &this->m_handle.m_atomicQueues[index];
727  FW_ASSERT(aq->isCreated(), this->m_handle.m_id, testPriority);
728  if (aq->getSize() == 0) {
729  continue;
730  }
731  const bool dequeued = aq->dequeue(destination, capacity, actualSize);
732  if (dequeued) {
733  priority = testPriority;
735  }
736  }
737 
738  // No message found
739  if (blockType == QueueInterface::BlockingType::BLOCKING) {
740  FW_ASSERT(this->m_handle.m_notEmptySem != nullptr, this->m_handle.m_id);
743  static_cast<FwAssertArgType>(semStatus));
744  } else {
745  return QueueInterface::Status::EMPTY;
746  }
747  }
748 
749  // Should never reach here - loop guard prevents infinite loop
750  FW_ASSERT(false, this->m_handle.m_id, LOOP_GUARD_LIMIT);
751  return QueueInterface::Status::UNKNOWN_ERROR;
752 }
753 
755  FwSizeType total = 0;
756 
757  if (this->m_handle.m_atomicQueues != nullptr) {
759  static_cast<FwAssertArgType>(this->m_handle.m_numActivePriorities));
760  for (FwSizeType i = 0; i < this->m_handle.m_numActivePriorities; ++i) {
761  const Types::AtomicQueue* atomicQueue = &this->m_handle.m_atomicQueues[i];
762  if (atomicQueue->isCreated()) {
763  total += atomicQueue->getSize();
764  }
765  }
766  }
767 
768  return total;
769 }
770 
772  // Return the maximum high water mark across all priorities
773  // MEMORY ORDERING: Use acquire to ensure visibility of latest HWM updates
774  U32 maxHwm = 0;
775  if (this->m_handle.m_highWaterMarks != nullptr) {
777  static_cast<FwAssertArgType>(this->m_handle.m_numActivePriorities));
778  for (FwSizeType i = 0; i < this->m_handle.m_numActivePriorities; ++i) {
779  U32 hwm = this->m_handle.m_highWaterMarks[i].load(std::memory_order_acquire);
780  if (hwm > maxHwm) {
781  maxHwm = hwm;
782  }
783  }
784  }
785  return static_cast<FwSizeType>(maxHwm);
786 }
787 
789  return &this->m_handle;
790 }
791 
792 } // namespace Generic
793 } // namespace Os
~CountingSemaphore() final
Destructor.
I8 m_priorityMap[Queue::MAX_PRIORITIES]
void enablePriority(FwQueuePriorityType priority)
Enable a specific priority (in the handle )
Operation succeeded.
Definition: Os.hpp:27
PlatformSizeType FwSizeType
I32 FwEnumStoreType
void teardown() override
teardown the queue
Status
status returned from the queue send function
Definition: Queue.hpp:30
static constexpr FwSizeType MAX_PRIORITIES
constexpr U32 LOOP_GUARD_LIMIT
QueueHandle parent class.
Definition: Queue.hpp:19
int8_t I8
8-bit signed integer
Definition: BasicTypes.h:51
static void configure(QueueConfig *queueConfigs, FwSizeType numQueueConfigs, bool required, FwEnumStoreType allocatorId)
Register per-priority sizes for component instances which do queueing with multiple priorities...
~AtomicQueue()
AtomicQueue destructor.
Definition: AtomicQueue.cpp:43
static constexpr FwSizeType DEFAULT_PRIORITY
FwSizeType getSize() const
Get the current number of elements in the queue.
static MemAllocatorRegistry & getInstance()
get the singleton registry
QueueHandle * getHandle() override
return the underlying queue handle (implementation specific)
bool enqueueBlocking(const U8 *buffer, FwSizeType size, bool blockIfFull)
Enqueue with optional blocking (multi-producer safe, O(1))
virtual ~PriorityMemQueue()
default queue destructor
PriorityMemQueueHandle m_handle
static void updateHighWaterMark(std::atomic< U32 > *highWaterMarks, FwSizeType index, U32 currentDepth, FwEnumStoreType queueId)
Update per-priority high water mark atomically.
A lock-free MPMC FIFO circular buffer with fixed-size buffer storage.
Definition: AtomicQueue.hpp:51
Status receive(U8 *destination, FwSizeType capacity, BlockingType blockType, FwSizeType &actualSize, FwQueuePriorityType &priority) override
receive a message from the queue
REQUIRED: required for Os::Queue memory allocation when using queues that allocate memory...
MemAllocator & getAnAllocator(const MemoryAllocation::MemoryAllocatorType type)
static Types::AtomicQueue * resolvePriorityQueue(PriorityMemQueueHandle &handle, FwQueuePriorityType &priority, FwEnumStoreType queueId, bool requirePrioritySizing)
Resolve priority to a valid AtomicQueue, fallback to DEFAULT if needed.
static void resetConfig()
Reset static configuration (test environments only)
FwSizeType getMessagesAvailable() const override
get number of messages available
void init()
Initialize the handle.
void * checkedAllocate(const FwEnumStoreType identifier, FwSizeType &size, bool &recoverable, FwSizeType alignment=alignof(std::max_align_t))
void deallocateArrays(Fw::MemAllocator &allocator, FwEnumStoreType allocatorId)
Deallocate arrays for priority data.
Status create(FwEnumStoreType id, const Fw::ConstStringBase &name, FwSizeType depth, FwSizeType messageSize) override
create queue storage
bool allocateArrays(Fw::MemAllocator &allocator, FwEnumStoreType allocatorId)
Allocate arrays for priority data (sparse allocation) Uses m_priorityMap to determine which prioritie...
bool isCreated() const
Check if queue has been successfully created.
Os::CountingSemaphore * m_notEmptySem
uint8_t U8
8-bit unsigned integer
Definition: BasicTypes.h:54
Configuration for a queue with multiple priorities.
BlockingType
message type
Definition: Queue.hpp:46
PlatformQueuePriorityType FwQueuePriorityType
The type of queue priorities used.
critical data stored for priority queue
static constexpr U32 priorityBitMask(FwQueuePriorityType priority)
Get the bit mask for a priority.
Memory Allocation base class.
I8 getPriorityIndex(FwQueuePriorityType priority) const
Get array index for a priority (inline for performance)
void teardown()
Teardown the queue and free allocated memory.
A read-only abstract superclass for StringBase.
void disablePriority(FwQueuePriorityType priority)
Disable a specific priority.
static void validateQueueConfigs(PriorityMemQueue::QueueConfig *queueConfigs, FwSizeType numQueueConfigs)
Validate queue configuration structures.
void teardownInternal()
teardown the queue
FwSizeType getBufferSize() const
Get the buffer size for each message.
bool enqueue(const U8 *buffer, FwSizeType size)
Enqueue a message (multi-producer safe, non-blocking, O(1))
Status wait() override
wait (decrement), blocking if count is zero
Defines a base class for a memory allocator for classes.
Status post() override
post (increment) the semaphore, potentially waking a waiting thread
void create(FwSizeType numBuffers, FwSizeType bufferSize, Fw::MemAllocator &allocator, FwEnumStoreType allocatorId)
Create the queue with embedded buffer storage.
Definition: AtomicQueue.cpp:47
static I32 findMSB(U32 value)
Find most significant bit set (IPC-style priority finding)
virtual void deallocate(const FwEnumStoreType identifier, void *ptr)=0
bool dequeue(U8 *buffer, FwSizeType capacity, FwSizeType &actualSize)
Dequeue a message (multi-consumer safe, non-blocking, O(1))
Status send(const U8 *buffer, FwSizeType size, FwQueuePriorityType priority, BlockingType blockType) override
send a message into the queue
#define FW_ASSERT(...)
Definition: Assert.hpp:14
PriorityMemQueue()
queue interface constructor - initializes handle
FwSizeType getMessageHighWaterMark() const override
get maximum messages stored at any given time