SatCat5
multi_buffer.cc
1 // Copyright 2024-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/interrupts.h>
7 #include <satcat5/multi_buffer.h>
8 #include <satcat5/utils.h>
9 
22 
23 // MultiPacket allocation is pulled from the same pool as chunk
24 // allocation, so we require the sizes to be compatible.
25 static_assert(sizeof(MultiPacket) <= sizeof(MultiChunk),
26  "MultiChunk must be large enough to reinterpret as a MultiPacket.");
27 
28 // Label for AtomicLock statistics tracking.
29 static const char* LBL_MBUFF = "MBUFF";
30 
31 satcat5::io::ArrayRead MultiPacket::peek() const {
32  unsigned max_peek = min_unsigned(SATCAT5_MBUFF_CHUNK, m_length);
33  return satcat5::io::ArrayRead(m_chunks.head()->m_data, max_peek);
34 }
35 
37  MultiPacket::Reader rd(this);
38  return rd.copy_and_finalize(wr);
39 }
40 
42  : m_read_pos(0)
43  , m_read_rem(0)
44  , m_read_pkt(0)
45  , m_read_chunk(0)
46 {
47  read_reset(pkt);
48 }
49 
51  // Reset read state and load the first chunk if applicable.
52  m_read_pos = 0;
53  m_read_pkt = packet;
54  if (packet) {
55  m_read_rem = packet->m_length;
56  m_read_chunk = packet->m_chunks.head();
57  } else {
58  m_read_rem = 0;
59  m_read_chunk = 0;
60  }
61 }
62 
64  // Remaining bytes in current packet.
65  return m_read_rem;
66 }
67 
68 bool MultiPacket::Reader::read_bytes(unsigned nbytes, void* dst) {
69  // Read one chunk at a time until finished...
70  if (nbytes > m_read_rem) return false;
71  u8* dst8 = (u8*)dst;
72  while (nbytes) {
73  // Stop at end of request or end of chunk, whichever comes first.
74  unsigned chunk = SATCAT5_MBUFF_CHUNK - m_read_pos;
75  unsigned nread = min_unsigned(nbytes, chunk);
76  if (dst8) memcpy(dst8, m_read_chunk->m_data + m_read_pos, nread);
77  // Advance to the next chunk?
78  if (nread == chunk) m_read_chunk = m_read_pkt->m_chunks.next(m_read_chunk);
79  m_read_pos = modulo_add_uns(m_read_pos + nread, SATCAT5_MBUFF_CHUNK);
80  // Increment the read/write position.
81  if (dst8) dst8 += nread;
82  nbytes -= nread;
83  m_read_rem -= nread;
84  }
85  return true;
86 }
87 
88 bool MultiPacket::Reader::read_consume(unsigned nbytes) {
89  return read_bytes(nbytes, 0);
90 }
91 
93  read_reset(m_read_pkt);
94 }
95 
97  // Read a single byte from the current chunk.
98  --m_read_rem;
99  u8 temp = m_read_chunk->m_data[m_read_pos];
100  // Advance to the next chunk if applicable.
101  if (++m_read_pos >= SATCAT5_MBUFF_CHUNK) {
102  m_read_chunk = m_read_pkt->m_chunks.next(m_read_chunk);
103  m_read_pos = 0;
104  }
105  return temp;
106 }
107 
109  : m_write_pos(0)
110  , m_write_rem(pkt->m_length)
111  , m_write_tot(0)
112  , m_write_chunk(pkt->m_chunks.head())
113 {
114  // Nothing else to initialize.
115 }
116 
118  return m_write_rem;
119 }
120 
121 void MultiPacket::Overwriter::write_bytes(unsigned nbytes, const void* src) {
122  // Write one chunk at a time until finished...
123  if (nbytes > m_write_rem) return;
124  u8* src8 = (u8*)src;
125  while (nbytes) {
126  // Stop at end of request or end of chunk, whichever comes first.
127  unsigned chunk = SATCAT5_MBUFF_CHUNK - m_write_pos;
128  unsigned ncopy = min_unsigned(nbytes, chunk);
129  memcpy(m_write_chunk->m_data + m_write_pos, src8, ncopy);
130  // Advance to the next chunk?
131  if (ncopy == chunk) m_write_chunk = ListCore::next(m_write_chunk);
132  m_write_pos = modulo_add_uns(m_write_pos + ncopy, SATCAT5_MBUFF_CHUNK);
133  // Increment the read/write position.
134  src8 += ncopy;
135  nbytes -= ncopy;
136  m_write_rem -= ncopy;
137  m_write_tot += ncopy;
138  }
139 }
140 
142  // Write a single byte to the current chunk.
143  --m_write_rem; ++m_write_tot;
144  m_write_chunk->m_data[m_write_pos] = data;
145  // Advance to the next chunk if applicable.
146  if (++m_write_pos >= SATCAT5_MBUFF_CHUNK) {
147  m_write_chunk = ListCore::next(m_write_chunk);
148  m_write_pos = 0;
149  }
150 }
151 
152 MultiBuffer::MultiBuffer(u8* buff, unsigned nbytes)
153  : m_free_bytes(0)
154  , m_pcount(0)
155  , m_free_chunks()
156  , m_read_ports()
157  , m_rcvd_packets()
158 {
159  // Initialize the list of free sub-buffers.
160  MultiChunk* temp = reinterpret_cast<MultiChunk*>(buff);
161  while (nbytes >= sizeof(MultiChunk)) {
162  m_free_chunks.add(temp);
163  m_free_bytes += SATCAT5_MBUFF_CHUNK;
164  ++temp; nbytes -= sizeof(MultiChunk);
165  }
166 }
167 
169  // Compare the list of free chunks against m_free_count.
170  AtomicLock lock(LBL_MBUFF);
171  if (m_free_chunks.has_loop()) return false;
172  unsigned free_count = m_free_chunks.len() * SATCAT5_MBUFF_CHUNK;
173  return free_count == m_free_bytes;
174 }
175 
177  // Push new packet onto the thread-safe delivery queue.
178  {
179  AtomicLock lock(LBL_MBUFF);
180  packet->m_pcount = ++m_pcount;
181  m_rcvd_packets.push_back(packet);
182  }
183  // Calling deliver() directly is too much work for an ISR.
184  // Instead, request deferred callback to poll_demand().
185  request_poll();
186  return true;
187 }
188 
190  // Pop next packet from the thread-safe delivery queue.
191  AtomicLock lock(LBL_MBUFF);
192  return m_rcvd_packets.pop_front();
193 }
194 
196  // Delivery processing for each packet, using child's "deliver" method:
197  // * Result = 0: No outputs accepted the packet, discard it immediately.
198  // * Result = 1: Matches new_packet() default, take no further action.
199  // This code may be used safely if the packet has already been freed.
200  // * Result > 1: Multiple outputs accepted the packet, update m_refct.
201  while (MultiPacket* pkt = dequeue()) {
202  unsigned result = deliver(pkt);
203  if (result == 0) free_packet(pkt);
204  if (result > 1) pkt->m_refct = result;
205  }
206 }
207 
209  // Attempt to deliver the new packet to every port.
210  // (Most child classes will override this behavior.)
211  unsigned count = 0;
212  MultiReader* ptr = m_read_ports.head();
213  while (ptr) {
214  if (ptr->accept(packet)) ++count;
215  ptr = m_read_ports.next(ptr);
216  }
217  return count;
218 }
219 
221  // Pop the next item (if any) from the list of free buffers.
222  AtomicLock lock(LBL_MBUFF);
223  MultiChunk* tmp = m_free_chunks.pop_front();
224  if (tmp) m_free_bytes -= SATCAT5_MBUFF_CHUNK;
225  return tmp;
226 }
227 
229  // Request a free buffer, treating the pointer as a MultiPacket.
230  // (Also pre-allocate the first chunk for the working buffer.)
231  MultiPacket* pkt = reinterpret_cast<MultiPacket*>(new_chunk());
232  if (pkt) {
233  pkt->m_chunks.reset(new_chunk());
234  pkt->m_length = 0;
235  pkt->m_refct = 1;
236  pkt->m_priority = 0;
237  pkt->m_pcount = 0;
238  memset(pkt->m_user, 0, sizeof(pkt->m_user));
239  }
240  return pkt;
241 }
242 
244  // Count the number of buffers we're about to return.
245  unsigned count = 1 + packet->m_chunks.len();
246  // Thread-safe return of each chunk and the object itself.
247  AtomicLock lock(LBL_MBUFF);
248  m_free_bytes += count * SATCAT5_MBUFF_CHUNK;
249  m_free_chunks.add_list(packet->m_chunks);
250  m_free_chunks.add(reinterpret_cast<MultiChunk*>(packet));
251 }
252 
254  : Reader(0)
255  , m_src(src)
256  , m_next(0)
257  , m_port_enable(true)
258  , m_read_timeout(SATCAT5_MBUFF_TIMEOUT)
259 {
260  // Add ourselves to the list of active ports.
261  m_src->m_read_ports.add(this);
262 }
263 
264 #if SATCAT5_ALLOW_DELETION
265 MultiReader::~MultiReader() {
266  // Cleanup the active packet and the list of active ports.
267  // Note: Child destructor is called first and MUST clean up its own
268  // working queue, because it is no longer safe to call child methods.
269  if (get_packet()) pkt_free(get_packet());
270  m_src->m_read_ports.remove(this);
271 }
272 #endif
273 
275  // Default accepts all packets unless this port is disabled or full.
276  bool ok = m_port_enable && pkt_push(packet);
277  // If we were previously idle, load the packet and request follow-up.
278  if (ok && !get_packet()) {
279  pkt_init(pkt_pop());
280  request_poll();
281  }
282  return ok;
283 }
284 
286  // Discard all queued packets.
287  while (get_packet()) read_finalize();
288 }
289 
291  // Cleanup current packet and start the next one.
292  // (Ignore this request if there is no active packet.)
293  if (get_packet()) {
294  pkt_free(get_packet());
295  pkt_init(pkt_pop());
296  }
297 }
298 
300  // Reset read state, then restart or cancel the watchdog timer.
301  read_reset(packet);
302  if (packet) {
304  } else {
305  timer_stop();
306  }
307 }
308 
310  // Decrement reference counter, free when it reaches zero.
311  if (--(packet->m_refct) == 0) m_src->free_packet(packet);
312 }
313 
315  // Watchdog timeout, discard all packets to prevent resource hogging.
316  // (The most likely cause is a UART port that's stuck or disconnected.)
317  flush();
318 }
319 
321  : MultiReader(src)
322  , m_queue_rdidx(0)
323  , m_queue_count(0)
324  , m_queue{0}
325 {
326  // Nothing else to initialize.
327 }
328 
329 #if SATCAT5_ALLOW_DELETION
330 MultiReaderSimple::~MultiReaderSimple() {
331  // Free any packets still waiting in the queue.
332  while (auto pkt = pkt_pop()) pkt_free(pkt);
333 }
334 #endif
335 
337  AtomicLock lock(LBL_MBUFF);
338  // Is this port able to accept new data?
339  if (m_queue_count >= SATCAT5_MBUFF_RXPKT) return false;
340  // Push the new pointer onto the circular buffer.
341  unsigned wridx = modulo_add_uns(m_queue_rdidx + m_queue_count, SATCAT5_MBUFF_RXPKT);
342  m_queue[wridx] = packet;
343  ++m_queue_count;
344  return true;
345 }
346 
348  AtomicLock lock(LBL_MBUFF);
349  // Pop first element unless the queue is empty.
350  if (m_queue_count == 0) return 0;
352  // Update the circular buffer state.
353  m_queue_rdidx = modulo_add_uns(m_queue_rdidx + 1, SATCAT5_MBUFF_RXPKT);
354  --m_queue_count;
355  return next;
356 }
357 
359  : MultiReader(src)
360  , m_heap_count(0)
361  , m_heap{0}
362 {
363  // Nothing else to initialize.
364 }
365 
366 #if SATCAT5_ALLOW_DELETION
367 MultiReaderPriority::~MultiReaderPriority() {
368  // Free any packets still waiting in the queue.
369  while (auto pkt = pkt_pop()) pkt_free(pkt);
370 }
371 #endif
372 
374  // Each node is a binary-tree heap is greater than its immediate children.
375  // (This is necessary and sufficient to show the entire tree is correct.)
376  for (unsigned a = 0 ; a < m_heap_count ; ++a) {
377  u32 pa = offset_priority(a);
378  u32 pl = offset_priority(2*a + 1);
379  u32 pr = offset_priority(2*a + 2);
380  if (pa < pl || pa < pr) return false;
381  }
382  return true;
383 }
384 
386  AtomicLock lock(LBL_MBUFF);
387  // Is this port able to accept new data?
388  if (m_heap_count >= SATCAT5_MBUFF_RXPKT) return false;
389  // Push the new pointer onto the end of the heap.
390  unsigned idx = m_heap_count++;
391  m_heap[idx] = packet;
392  // Swap elements as needed to restore binary-tree sort.
393  // https://en.wikipedia.org/wiki/Binary_heap#Insert
394  while (idx > 0) {
395  unsigned parent = (idx - 1) / 2;
396  if (offset_priority(parent) >= offset_priority(idx)) break;
397  idx = swap_index(idx, parent);
398  }
399  return true;
400 }
401 
403  AtomicLock lock(LBL_MBUFF);
404  // Pop root element unless the heap is empty.
405  if (m_heap_count == 0) return 0;
406  MultiPacket* next = m_heap[0];
407  // Move last remaining element to the root of the tree.
408  m_heap[0] = m_heap[--m_heap_count];
409  // Swap elements as needed to restore binary-tree sort.
410  // https://en.wikipedia.org/wiki/Binary_heap#Extract
411  unsigned idx = 0;
412  while (idx < m_heap_count) {
413  unsigned ll = 2*idx + 1; // Index of left child
414  unsigned rr = 2*idx + 2; // Index of right child
415  unsigned pi = offset_priority(idx);
416  unsigned pl = offset_priority(ll);
417  unsigned pr = offset_priority(rr);
418  if ((pi > pl) && (pi > pr)) break; // Stop once sorted
419  idx = swap_index(idx, pl >= pr ? ll : rr);
420  }
421  return next;
422 }
423 
424 u32 MultiReaderPriority::offset_priority(unsigned idx) const {
425  // Calculate effective packet priority, with tiebreaker by age.
426  // Note: Difference in u16 pcount values will wrap correctly, unless
427  // a low priority packet is stuck behind 32k high-priority packets.
428  if (idx >= m_heap_count) return 0; // No such element?
429  u32 age = (m_src->get_pcount() - m_heap[idx]->m_pcount) & 0x7FFF;
430  u32 pri = m_heap[idx]->m_priority;
431  return 65536 * pri + age + 1;
432 }
433 
434 
435 unsigned MultiReaderPriority::swap_index(unsigned prev, unsigned next) {
436  MultiPacket* tmp = m_heap[prev];
437  m_heap[prev] = m_heap[next];
438  m_heap[next] = tmp;
439  return next;
440 }
441 
443  : m_dst(dst)
444  , m_write_pkt(0)
445  , m_write_tail(0)
446  , m_write_pos(0)
447  , m_write_len(0)
448  , m_write_maxlen(SATCAT5_MBUFF_PKTLEN)
449  , m_write_timeout(SATCAT5_MBUFF_TIMEOUT)
450 {
451  // Nothing else to initialize.
452 }
453 
454 #if SATCAT5_ALLOW_DELETION
455 MultiWriter::~MultiWriter() {
456  // Cleanup any work in progress.
458 }
459 #endif
460 
461 void MultiWriter::set_priority(u16 priority) {
462  if (m_write_pkt) m_write_pkt->m_priority = priority;
463 }
464 
466  // Remaining space may be limited by policy or by buffer space.
467  if (m_write_len >= m_write_maxlen) return 0;
468  unsigned pkrem = m_write_maxlen - m_write_len;
469  unsigned alloc = m_dst->m_free_bytes;
470  if (m_write_tail) alloc += SATCAT5_MBUFF_CHUNK - m_write_pos;
471  return min_unsigned(pkrem, alloc);
472 }
473 
474 void MultiWriter::write_bytes(unsigned nbytes, const void* src) {
475  // Reset the watchdog timer.
477  // Abort writes that cannot be completed.
478  if (nbytes > get_write_space()) {write_overflow(); return;}
479  // Write one chunk at a time until finished...
480  const u8* src8 = (const u8*)src;
481  while (nbytes) {
482  // Are we able to write at least one more byte?
483  unsigned chunk = write_prep();
484  if (!chunk) break;
485  // Stop at end of request or end of chunk, whichever comes first.
486  unsigned nwrite = min_unsigned(nbytes, chunk);
487  memcpy(m_write_tail->m_data + m_write_pos, src8, nwrite);
488  // Increment the write position.
489  nbytes -= nwrite;
490  src8 += nwrite;
491  m_write_pos += nwrite;
492  m_write_len += nwrite;
493  }
494  // Unable to complete requested write?
495  if (nbytes) write_overflow();
496 }
497 
499  // Watchdog timeout waiting for a partial packet.
500  // (The most likely cause is a UART port that's stuck or disconnected.)
501  write_abort();
502 }
503 
505  // Free any open buffers and return to the idle state.
507  timer_stop();
508  m_write_pkt = 0;
509  m_write_tail = 0;
510  m_write_pos = 0;
511  m_write_len = 0;
512 }
513 
515  // Deliver valid packets to the MultiBuffer for processing.
516  // (This calls MultiBuffer::deliver() or an override of that method.)
517  MultiPacket* pkt = prepare_pkt();
518  return pkt && m_dst->enqueue(pkt);
519 }
520 
522  // Attempt delivery directly to the specified MultiReader.
523  // (This does NOT pass through MultiBuffer::deliver().)
524  MultiPacket* pkt = prepare_pkt();
525  bool rcvd = pkt && dst->accept(pkt);
526  // If the attempt failed, free associated memory.
527  if (pkt && !rcvd) m_dst->free_packet(pkt);
528  return rcvd;
529 }
530 
531 void MultiWriter::write_next(u8 data) {
532  // Reset the watchdog timer.
534  // Do we need to allocate additional memory?
535  if (write_prep()) {
536  // Write a single byte.
537  m_write_tail->m_data[m_write_pos] = data;
538  ++m_write_pos;
539  ++m_write_len;
540  } else {
541  // Note: This should be reachable only through interrupt race
542  // conditions, which are difficult to reproduce in unit tests.
543  write_overflow(); // GCOVR_EXCL_LINE
544  }
545 }
546 
548  // Flag the current packet as undeliverable, and free the working buffer.
549  // Continued writes are discarded until write_finalize() or write_abort().
550  m_write_len = UINT_MAX;
551  if (m_write_pkt) {
553  m_write_pkt = 0;
554  m_write_tail = 0;
555  }
556 }
557 
559  if (m_write_len >= m_write_maxlen) {
560  // Overflow state, abort immediately.
561  // Note: This should be reachable only through interrupt race
562  // conditions, which are difficult to reproduce in unit tests.
563  return 0; // GCOVR_EXCL_LINE
564  } else if (!m_write_pkt) {
565  // Attempt to open a new packet.
566  m_write_len = 0;
567  m_write_pos = 0;
569  if (!m_write_pkt) return 0;
570  // Update pointer to the first/last/only allocated chunk.
571  // If new_packet() wasn't able to allocate one, abort.
572  m_write_tail = m_write_pkt->m_chunks.head();
573  if (!m_write_tail) return 0;
574  } else if (m_write_pos >= SATCAT5_MBUFF_CHUNK) {
575  // Attempt to allocate another chunk.
576  MultiChunk* tmp = m_dst->new_chunk();
577  if (!tmp) return 0;
578  // Add the new chunk to the end of the linked list.
579  // We know the tail, so insert_after() is faster than push_back().
580  m_write_pos = 0;
581  m_write_pkt->m_chunks.insert_after(m_write_tail, tmp);
582  m_write_tail = tmp;
583  }
584  return SATCAT5_MBUFF_CHUNK - m_write_pos;
585 }
586 
588  MultiPacket* tmp = nullptr;
589  if (m_write_pkt && m_write_len < UINT_MAX) {
590  // Return value is the active packet.
591  tmp = m_write_pkt;
592  tmp->m_length = m_write_len;
593  // Reset internal state.
594  timer_stop();
595  m_write_pkt = 0;
596  m_write_tail = 0;
597  m_write_pos = 0;
598  m_write_len = 0;
599  } else {
600  // Reset to a known-good state.
601  write_abort();
602  }
603  return tmp;
604 }
605 
609  u16 priority)
610  : MultiWriter(buf), m_dst(dst), m_priority(priority)
611 {
612  // Nothing else to initialize.
613 }
614 
616  if (m_priority) MultiWriter::set_priority(m_priority);
617  return MultiWriter::write_bypass(m_dst);
618 }
Ephemeral Readable interface for a simple array.
Definition: io_readable.h:206
A multi-source, multi-sink packet buffer.
Definition: multi_buffer.h:198
void free_packet(satcat5::io::MultiPacket *packet)
Immediately free memory associated with this packet.
satcat5::io::MultiPacket * new_packet()
Memory allocation.
u16 get_pcount()
Current value of the packet counter.
Definition: multi_buffer.h:211
satcat5::io::MultiChunk * new_chunk()
Memory allocation.
bool consistency() const
Internal consistency self-test (Optional).
MultiBuffer(u8 *buff, unsigned nbytes)
Configure this object and link to the working buffer.
void poll_demand() override
Deferred event handler, called after request().
virtual unsigned deliver(satcat5::io::MultiPacket *packet)
Deliver a complete packet to any number of output port(s).
satcat5::io::MultiPacket * dequeue()
Event handler for deferred packet delivery.
bool enqueue(satcat5::io::MultiPacket *packet)
Queue an incoming packet for deferred processing.
void write_next(u8 data) override
Write the next byte to the underlying buffer or device.
void write_bytes(unsigned nbytes, const void *src) override
Write 0 or more bytes from a buffer.
Overwriter(satcat5::io::MultiPacket *pkt)
Create a new Writer object.
unsigned get_write_space() const override
How many bytes can be written without blocking?
Barebones class for reading data from a MultiPacket.
Definition: multi_buffer.h:129
unsigned get_read_ready() const override
How many bytes can be read without blocking?
Definition: multi_buffer.cc:63
bool read_consume(unsigned nbytes) override
Read and discard 0 or more bytes.
Definition: multi_buffer.cc:88
Reader(const satcat5::io::MultiPacket *pkt=0)
Create a new Reader object.
Definition: multi_buffer.cc:41
u8 read_next() override
Read the next byte from the underlying buffer or device.
Definition: multi_buffer.cc:96
bool read_bytes(unsigned nbytes, void *dst) override
Read 0 or more bytes into a buffer.
Definition: multi_buffer.cc:68
void read_reset(const satcat5::io::MultiPacket *pkt)
Reset read state for the designated packet.
Definition: multi_buffer.cc:50
satcat5::io::MultiPacket * get_packet() const
Get a pointer to the current packet, if active.
Definition: multi_buffer.h:135
void read_finalize() override
Consume any remaining bytes in this frame, if applicable.
Definition: multi_buffer.cc:92
A port for reading from a MultiBuffer object.
Definition: multi_buffer.h:258
virtual bool accept(satcat5::io::MultiPacket *packet)
Accept a packet from the source buffer? Default accepts all packets unless this port is disabled or f...
bool m_port_enable
Internal state.
Definition: multi_buffer.h:329
void timer_event() override
Timeouts help prevent resource-hogging.
unsigned m_read_timeout
Internal state.
Definition: multi_buffer.h:330
void read_finalize() override
Consume any remaining bytes in this frame, if applicable.
satcat5::io::MultiBuffer *const m_src
Pointer to the source buffer.
Definition: multi_buffer.h:319
void flush()
Discard all queued packets.
MultiReader(satcat5::io::MultiBuffer *src)
Constructor and destructor are only accessible to children.
void pkt_init(satcat5::io::MultiPacket *packet)
Helper function for starting a new packet, or NULL to stop.
virtual satcat5::io::MultiPacket * pkt_pop()=0
Choose the next packet to start reading, or NULL to stop.
void pkt_free(satcat5::io::MultiPacket *packet)
Decrement reference count, free when it reaches zero.
virtual bool pkt_push(satcat5::io::MultiPacket *packet)=0
Push a packet onto the end of a queue or similar data structure.
A variant of MultiReader that follows priority ordering.
Definition: multi_buffer.h:365
unsigned m_heap_count
Binary heap sorted by increasing priority.
Definition: multi_buffer.h:395
satcat5::io::MultiPacket * pkt_pop() override
Implement the push() and pop() methods.
satcat5::io::MultiPacket * m_heap[SATCAT5_MBUFF_RXPKT]
Binary heap sorted by increasing priority.
Definition: multi_buffer.h:396
bool consistency() const
Internal consistency self-test (Optional).
bool pkt_push(satcat5::io::MultiPacket *pkt) override
Implement the push() and pop() methods.
u32 offset_priority(unsigned idx) const
Return modified priority, with tie-breaker using packet count.
unsigned swap_index(unsigned prev, unsigned next)
Swap two elements and return the new index.
MultiReaderPriority(satcat5::io::MultiBuffer *src)
Create this port and link it to the source buffer.
A variant of MultiReader with a simple first-in, first-out queue.
Definition: multi_buffer.h:336
satcat5::io::MultiPacket * pkt_pop() override
Implement the push() and pop() methods.
unsigned m_queue_rdidx
Queue based on a circular buffer.
Definition: multi_buffer.h:357
satcat5::io::MultiPacket * m_queue[SATCAT5_MBUFF_RXPKT]
Queue based on a circular buffer.
Definition: multi_buffer.h:359
bool pkt_push(satcat5::io::MultiPacket *pkt) override
Implement the push() and pop() methods.
unsigned m_queue_count
Queue based on a circular buffer.
Definition: multi_buffer.h:358
MultiReaderSimple(satcat5::io::MultiBuffer *src)
Create this port and link it to the source buffer.
MultiWriter adapter for bypass mode.
Definition: multi_buffer.h:481
bool write_finalize() override
Override redirects to write_bypass().
MultiWriterBypass(satcat5::io::MultiBuffer *buf, satcat5::io::MultiReader *dst, u16 priority=0)
Link this object to a buffer and a destination.
A port for writing to a MultiBuffer object.
Definition: multi_buffer.h:405
void write_bytes(unsigned nbytes, const void *src) override
Write 0 or more bytes from a buffer.
void set_priority(u16 priority)
Set priority of the current packet.
bool write_finalize() override
Mark end of frame and release temporary working data.
MultiWriter(satcat5::io::MultiBuffer *dst)
Create this port and link it to the destination buffer.
unsigned m_write_pos
Current write state.
Definition: multi_buffer.h:471
satcat5::io::MultiBuffer *const m_dst
Pointer to the destination buffer.
Definition: multi_buffer.h:465
satcat5::io::MultiChunk * m_write_tail
Current write state.
Definition: multi_buffer.h:470
void timer_event() override
Timeouts help prevent resource-hogging.
unsigned get_write_space() const override
How many bytes can be written without blocking?
void write_overflow() override
Optional error handling for write overflow.
bool write_bypass(satcat5::io::MultiReader *dst)
Deliver data directly to the designated MultiReader.
unsigned m_write_maxlen
Current write state.
Definition: multi_buffer.h:473
satcat5::io::MultiPacket * m_write_pkt
Current write state.
Definition: multi_buffer.h:469
unsigned m_write_timeout
Current write state.
Definition: multi_buffer.h:474
unsigned write_prep()
Open a new packet or allocate additional buffers.
unsigned m_write_len
Current write state.
Definition: multi_buffer.h:472
void write_next(u8 data) override
Write the next byte to the underlying buffer or device.
satcat5::io::MultiPacket * prepare_pkt()
Prepare packet for delivery and reset internal state.
void write_abort() override
If possible, abort the current partially-written packet.
bool copy_and_finalize(satcat5::io::Writeable *dst, satcat5::io::CopyMode mode=CopyMode::PACKET)
Copy data to a Writeable object, then finalize.
Definition: io_readable.cc:234
Abstract API for writing byte-streams and packets.
Definition: io_writeable.h:24
Automatic lock or mutex.
void request_poll()
Call this method to request polling at a later time.
Definition: polling.cc:208
void timer_stop()
Stop all future notifications.
Definition: polling.cc:326
void timer_once(unsigned msec)
Configure a one-time notification after X milliseconds.
Definition: polling.cc:316
Helper functions for manipulating singly-linked lists.
Definition: list.h:52
unsigned len() const
Traverse the linked list to count its length.
Definition: list.h:257
void add_list(satcat5::util::List< T > &other)
Add each item from "list2" onto "list1", destroying "list2".
Definition: list.h:226
T * next(const T *item) const
Fetch pointer to the next item.
Definition: list.h:261
void push_back(T *item)
Add a new item at the tail of the list.
Definition: list.h:273
void reset(T *item=0)
Discard list contents and reset to empty or a single item.
Definition: list.h:281
bool has_loop() const
Check if the linked list loops back on itself, using the two-pointer "tortoise and hare" algorithm.
Definition: list.h:245
void insert_after(T *where, T *item)
Insert a new item just after the designated position.
Definition: list.h:249
void add(T *item)
Add new item to front or back, whichever is simpler.
Definition: list.h:221
T * pop_front()
Remove the item at the head of the list.
Definition: list.h:265
void remove(T *item)
Remove the designated item from the list.
Definition: list.h:277
Multi-source, multi-sink packet buffer.
Platform-agnostic API for interrupt management.
Data-structure representing a single fine-grained memory block.
Definition: multi_buffer.h:75
A packet is a linked-list of memory blocks, plus metadata.
Definition: multi_buffer.h:91
unsigned m_length
Packet length in bytes.
Definition: multi_buffer.h:102
u16 m_priority
Packet priority.
Definition: multi_buffer.h:104
u16 m_pcount
Packet counter.
Definition: multi_buffer.h:105
u32 m_user[SATCAT5_MBUFF_USER]
Packet metadata.
Definition: multi_buffer.h:107
bool copy_to(satcat5::io::Writeable *wr) const
Copy the packet contents to the specified destination.
Definition: multi_buffer.cc:36
unsigned m_refct
Reference counter.
Definition: multi_buffer.h:103
Miscellaneous mathematical utility functions.
constexpr unsigned min_unsigned(unsigned a, unsigned b)
Min and max functions.
Definition: utils.h:111
constexpr unsigned modulo_add_uns(unsigned sum, unsigned m)
Modulo addition function.
Definition: utils.h:182