SuperTinyKernel™ RTOS 1.08.x
Lightweight, high-performance, deterministic, bare-metal C++ RTOS for resource-constrained embedded systems. MIT Open Source License.
Loading...
Searching...
No Matches
stk_sync_pipe.h
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#ifndef STK_SYNC_PIPE_H_
11#define STK_SYNC_PIPE_H_
12
13#include <type_traits> // for std::is_scalar
14
15#include "stk_sync_cv.h"
16
22
23namespace stk {
24namespace sync {
25
77class Pipe : public ITraceable
78{
79public:
82 static const size_t CAPACITY_MAX = 0xFFFEU;
83
89 explicit Pipe(uint8_t *buf, size_t capacity, size_t element_size);
90
97 STK_VIRT_DTOR ~Pipe() = default;
98
111 EWaitResult WriteEx(const void *data, Timeout timeout_ticks = WAIT_INFINITE);
112
123 bool Write(const void *data, Timeout timeout_ticks = WAIT_INFINITE)
124 {
125 return (WriteEx(data, timeout_ticks) == WAIT_RESULT_SIGNAL);
126 }
127
135 bool TryWrite(const void *data) { return Write(data, NO_WAIT); }
136
154 EWaitResult WriteBulkEx(const void *src, size_t count, size_t &written,
155 Timeout timeout_ticks = WAIT_INFINITE);
156
179 size_t WriteBulk(const void *src, size_t count, Timeout timeout_ticks = WAIT_INFINITE)
180 {
181 size_t written = 0U;
182 const EWaitResult result = WriteBulkEx(src, count, written, timeout_ticks);
183 STK_UNUSED(result);
184 return written;
185 }
186
195 size_t TryWriteBulk(const void *src, size_t count) { return WriteBulk(src, count, NO_WAIT); }
196
209 EWaitResult ReadEx(void *data, Timeout timeout_ticks = WAIT_INFINITE);
210
221 bool Read(void *data, Timeout timeout_ticks = WAIT_INFINITE)
222 {
223 return (ReadEx(data, timeout_ticks) == WAIT_RESULT_SIGNAL);
224 }
225
233 bool TryRead(void *data) { return Read(data, NO_WAIT); }
234
252 EWaitResult ReadBulkEx(void *dst, size_t count, size_t &read_count,
253 Timeout timeout_ticks = WAIT_INFINITE);
254
278 size_t ReadBulk(void *dst, size_t count, Timeout timeout_ticks = WAIT_INFINITE)
279 {
280 size_t read_count = 0U;
281 const EWaitResult result = ReadBulkEx(dst, count, read_count, timeout_ticks);
282 STK_UNUSED(result);
283 return read_count;
284 }
285
293 size_t TryReadBulk(void *dst, size_t count) { return ReadBulk(dst, count, NO_WAIT); }
294
316 EWaitResult ReadBulkTriggeredEx(void *dst, size_t trigger, size_t max_count, size_t &read_count,
317 Timeout timeout_ticks = WAIT_INFINITE);
318
342 size_t ReadBulkTriggered(void *dst, size_t trigger, size_t max_count,
343 Timeout timeout_ticks = WAIT_INFINITE)
344 {
345 size_t read_count = 0U;
346 const EWaitResult result = ReadBulkTriggeredEx(dst, trigger, max_count, read_count, timeout_ticks);
347 STK_UNUSED(result);
348 return read_count;
349 }
350
360 size_t TryReadBulkTriggered(void *dst, size_t max_count)
361 {
362 return ReadBulkTriggered(dst, 1U, max_count, NO_WAIT);
363 }
364
372 void Reset();
373
378 size_t GetCapacity() const { return m_capacity; }
379
384 size_t GetElementSize() const { return m_element_size; }
385
391 size_t GetCount() const { return m_count; }
392
397 size_t GetSpace() const { return (m_capacity - m_count); }
398
403 uint8_t *GetBuffer() { return m_buffer; }
404
410 bool IsEmpty() const { return (m_count == 0U); }
411
417 bool IsFull() const { return (m_count == m_capacity); }
418
427 bool IsStorageValid() const { return (m_buffer != nullptr); }
428
429private:
431
432 // Get a byte pointer to the raw storage for slot index \a idx.
433 uint8_t *Slot(size_t idx) const { return m_buffer + (idx * m_element_size); }
434
435 // Advance a ring-buffer index by one with wrap-around.
436 size_t Next(size_t idx) const { return (idx + 1U) % m_capacity; }
437
438 // Copy Min(count, m_count) elements into dst_bytes and update tail/count.
439 // Caller MUST hold the critical section. Returns the number of elements copied.
440 size_t DrainLocked(uint8_t *dst_bytes, size_t count);
441
442 uint8_t *m_buffer;
443 const size_t m_capacity;
444 const size_t m_element_size;
445 size_t m_count;
446 size_t m_head;
447 size_t m_tail;
450};
451
452// ---------------------------------------------------------------------------
453// Pipe: Constructor
454// ---------------------------------------------------------------------------
455
456inline Pipe::Pipe(uint8_t *buf, size_t capacity, size_t element_size)
457: m_buffer(buf),
458 m_capacity(capacity),
459 m_element_size(element_size),
460 m_count(0U),
461 m_head(0U),
462 m_tail(0U)
463{
464 STK_ASSERT(buf != nullptr);
465 STK_ASSERT(capacity >= 1U);
466 STK_ASSERT(capacity <= CAPACITY_MAX);
467 STK_ASSERT(element_size >= 1U);
468}
469
470// ---------------------------------------------------------------------------
471// Pipe: WriteEx
472// ---------------------------------------------------------------------------
473
474inline EWaitResult Pipe::WriteEx(const void *data, Timeout timeout_ticks)
475{
476 STK_ASSERT(data != nullptr);
478
481
482 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
483
484 // capture an absolute deadline once, before entering the wait loop; a different
485 // producer can steal the just-freed slot between NotifyOne_CS() and this task
486 // re-acquiring the critical section (Mesa-style wakeup, not a direct handoff),
487 // so re-passing the original timeout_ticks on each retry would silently restart it
488 const Timeout deadline = (timed_wait ?
489 static_cast<Timeout>(GetTicks() + timeout_ticks) : timeout_ticks);
490
491 while (m_count == m_capacity)
492 {
493 Timeout remaining = deadline;
494 if (timed_wait)
495 {
496 const Timeout now = static_cast<Timeout>(GetTicks());
497 remaining = (now >= deadline ? NO_WAIT : (deadline - now));
498 }
499
500 result = m_cv_not_full.WaitEx(cs_, remaining);
501 if (result != WAIT_RESULT_SIGNAL)
502 {
503 // timeout, cancellation or kernel failure: nothing is written
504 break;
505 }
506 }
507
508 if (result == WAIT_RESULT_SIGNAL)
509 {
511 m_head = Next(m_head);
512 m_count++;
513
514 // notify consumer that data is ready
515 m_cv_not_empty.NotifyOne_CS();
516 }
517
518 return result;
519}
520
521// ---------------------------------------------------------------------------
522// Pipe: WriteBulkEx
523// ---------------------------------------------------------------------------
524
525inline EWaitResult Pipe::WriteBulkEx(const void *src, size_t count, size_t &written, Timeout timeout_ticks)
526{
527 written = 0U;
528
529 // null source is rejected, an empty request is trivially satisfied
530 EWaitResult result = ((src != nullptr) ? WAIT_RESULT_SIGNAL : WAIT_RESULT_FAIL);
531
532 if ((src != nullptr) && (count != 0U))
533 {
534 const uint8_t *const src_bytes = static_cast<const uint8_t *>(src);
535 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
536
537 // capture an absolute deadline once, before entering the wait loop,
538 // preventing the timeout from resetting on intermediate partial writes
539 const Timeout deadline = (timed_wait ?
540 static_cast<Timeout>(GetTicks() + timeout_ticks) : timeout_ticks);
541
543
544 while (written < count)
545 {
546 // wait until there is at least 1 free slot
547 while (m_count == m_capacity)
548 {
549 Timeout remaining = deadline;
550 if (timed_wait)
551 {
552 const Timeout now = static_cast<Timeout>(GetTicks());
553 remaining = (now >= deadline ? NO_WAIT : (deadline - now));
554 }
555
556 const EWaitResult wr = m_cv_not_full.WaitEx(cs_, remaining);
557 if (wr != WAIT_RESULT_SIGNAL)
558 {
559 result = wr;
560 break; // break inner condition variable loop
561 }
562 }
563
564 // if the wait failed (timeout, cancellation), drop out of the chunk processing loop
565 if (result != WAIT_RESULT_SIGNAL)
566 {
567 break;
568 }
569
570 const size_t available = m_capacity - m_count;
571 const size_t to_write = ((count - written) < available) ? (count - written) : available;
572 const size_t first_part = m_capacity - m_head;
573
574 if (to_write <= first_part)
575 {
576 STK_MEMCPY(Slot(m_head), src_bytes + (written * m_element_size), to_write * m_element_size);
577 }
578 else
579 {
580 STK_MEMCPY(Slot(m_head), src_bytes + (written * m_element_size), first_part * m_element_size);
581 STK_MEMCPY(Slot(0U), src_bytes + ((written + first_part) * m_element_size), (to_write - first_part) * m_element_size);
582 }
583
584 written += to_write;
585 m_head = (m_head + to_write) % m_capacity;
586 m_count += to_write;
587
588 // notify consumers that data is ready
589 m_cv_not_empty.NotifyAll_CS();
590 }
591 }
592
593 return result;
594}
595
596// ---------------------------------------------------------------------------
597// Pipe: ReadEx
598// ---------------------------------------------------------------------------
599
600inline EWaitResult Pipe::ReadEx(void *data, Timeout timeout_ticks)
601{
602 STK_ASSERT(data != nullptr);
603
606
607 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
608
609 // capture an absolute deadline once, before entering the wait loop; a different
610 // consumer can steal the just-produced element between NotifyOne_CS() and this
611 // task re-acquiring the critical section (Mesa-style wakeup, not a direct handoff),
612 // so re-passing the original timeout_ticks on each retry would silently restart it
613 const Timeout deadline = (timed_wait ?
614 static_cast<Timeout>(GetTicks() + timeout_ticks) : timeout_ticks);
615
616 while (m_count == 0U)
617 {
618 Timeout remaining = deadline;
619 if (timed_wait)
620 {
621 const Timeout now = static_cast<Timeout>(GetTicks());
622 remaining = (now >= deadline ? NO_WAIT : (deadline - now));
623 }
624
625 result = m_cv_not_empty.WaitEx(cs_, remaining);
626 if (result != WAIT_RESULT_SIGNAL)
627 {
628 // timeout, cancellation or kernel failure: nothing is read
629 break;
630 }
631 }
632
633 if (result == WAIT_RESULT_SIGNAL)
634 {
636 m_tail = Next(m_tail);
637 m_count--;
638
639 // notify producer that space is now available
640 m_cv_not_full.NotifyOne_CS();
641 }
642
643 return result;
644}
645
646// ---------------------------------------------------------------------------
647// Pipe: DrainLocked (private helper, caller must hold the critical section)
648// ---------------------------------------------------------------------------
649
650inline size_t Pipe::DrainLocked(uint8_t *const dst_bytes, const size_t count)
651{
652 const size_t to_read = Min(count, m_count);
653 const size_t first_part = m_capacity - m_tail;
654
655 if (to_read <= first_part)
656 {
657 STK_MEMCPY(dst_bytes, Slot(m_tail), to_read * m_element_size);
658 }
659 else
660 {
661 STK_MEMCPY(dst_bytes, Slot(m_tail), first_part * m_element_size);
662 STK_MEMCPY(dst_bytes + (first_part * m_element_size), Slot(0U), (to_read - first_part) * m_element_size);
663 }
664
665 m_tail = (m_tail + to_read) % m_capacity;
666 m_count -= to_read;
667
668 m_cv_not_full.NotifyAll_CS();
669 return to_read;
670}
671
672// ---------------------------------------------------------------------------
673// Pipe: ReadBulkEx
674// ---------------------------------------------------------------------------
675
676inline EWaitResult Pipe::ReadBulkEx(void *dst, size_t count, size_t &read_count, Timeout timeout_ticks)
677{
678 read_count = 0U;
679
680 // null destination is rejected, an empty request is trivially satisfied
681 EWaitResult result = ((dst != nullptr) ? WAIT_RESULT_SIGNAL : WAIT_RESULT_FAIL);
682
683 if ((dst != nullptr) && (count != 0U))
684 {
685 uint8_t *const dst_bytes = static_cast<uint8_t *>(dst);
686 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
687
688 // capture an absolute deadline once, before entering the wait loop,
689 // preventing the timeout from resetting on intermediate partial reads
690 const Timeout deadline = (timed_wait ?
691 static_cast<Timeout>(GetTicks() + timeout_ticks) : timeout_ticks);
692
694
695 while (read_count < count)
696 {
697 // wait until there is at least 1 element available
698 while (m_count == 0U)
699 {
700 Timeout remaining = deadline;
701 if (timed_wait)
702 {
703 const Timeout now = static_cast<Timeout>(GetTicks());
704 remaining = (now >= deadline ? NO_WAIT : (deadline - now));
705 }
706
707 const EWaitResult wr = m_cv_not_empty.WaitEx(cs_, remaining);
708 if (wr != WAIT_RESULT_SIGNAL)
709 {
710 result = wr;
711 break; // break inner condition variable loop
712 }
713 }
714
715 // if the wait failed (timeout, cancellation), drop out of the chunk processing loop
716 if (result != WAIT_RESULT_SIGNAL)
717 {
718 break;
719 }
720
721 // drain data using the state tracker's relative byte offsets
722 read_count += DrainLocked(dst_bytes + (read_count * m_element_size), count - read_count);
723 }
724 }
725
726 return result;
727}
728
729// ---------------------------------------------------------------------------
730// Pipe: ReadBulkTriggeredEx
731// ---------------------------------------------------------------------------
732
733inline EWaitResult Pipe::ReadBulkTriggeredEx(void *dst, size_t trigger, size_t max_count, size_t &read_count,
734 Timeout timeout_ticks)
735{
736 read_count = 0U;
737
738 // null destination is rejected, an empty request is trivially satisfied
739 EWaitResult result = ((dst != nullptr) ? WAIT_RESULT_SIGNAL : WAIT_RESULT_FAIL);
740
741 if ((dst != nullptr) && (max_count != 0U))
742 {
743 // trigger must be in [1, max_count]
744 if (trigger == 0U) { trigger = 1U; }
745 if (trigger > max_count) { trigger = max_count; }
746
747 uint8_t *const dst_bytes = static_cast<uint8_t *>(dst);
748 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
749
750 // capture an absolute deadline once, before entering the wait loop,
751 // preventing the timeout from resetting on intermediate spurious wakeups
752 const Timeout deadline = (timed_wait ?
753 static_cast<Timeout>(GetTicks() + timeout_ticks) : timeout_ticks);
754
756
757 while (m_count < trigger)
758 {
759 Timeout remaining = deadline;
760 if (timed_wait)
761 {
762 const Timeout now = static_cast<Timeout>(GetTicks());
763 remaining = (now >= deadline ? NO_WAIT : (deadline - now));
764 }
765
766 const EWaitResult wr = m_cv_not_empty.WaitEx(cs_, remaining);
767 if (wr != WAIT_RESULT_SIGNAL)
768 {
769 result = wr;
770 break; // break the waiting loop on timeout, cancellation or failure
771 }
772 }
773
774 // whether we broke out via satisfying the trigger or a failed wait (timeout,
775 // cancellation), we drain whatever is currently available up to max_count
776 read_count = DrainLocked(dst_bytes, max_count);
777 }
778
779 return result;
780}
781
782// ---------------------------------------------------------------------------
783// Pipe: Reset
784// ---------------------------------------------------------------------------
785
786inline void Pipe::Reset()
787{
788 const ScopedCriticalSection cs_;
789
790 m_count = 0U;
791 m_head = 0U;
792 m_tail = 0U;
793
794 // wake all blocked producers: the pipe is now entirely empty, every slot is free
795 // note: we do not release readers here
796 m_cv_not_full.NotifyAll_CS();
797}
798
799// ---------------------------------------------------------------------------
800
847template <typename T, size_t N>
848class PipeT
849{
850public:
853 explicit PipeT() : m_buffer(), m_head(0U), m_tail(0U), m_count(0U), m_cv_not_empty(), m_cv_not_full()
854 {}
855
868 EWaitResult WriteEx(const T &data, Timeout timeout_ticks = WAIT_INFINITE)
869 {
872
873 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
874
875 // capture an absolute deadline once, before entering the wait loop; a different
876 // producer can steal the just-freed slot between NotifyOne_CS() and this task
877 // re-acquiring the critical section (Mesa-style wakeup, not a direct handoff),
878 // so re-passing the original timeout_ticks on each retry would silently restart it
879 const Timeout deadline = (timed_wait ?
880 static_cast<Timeout>(GetTicks() + timeout_ticks) : timeout_ticks);
881
882 while (m_count == N)
883 {
884 Timeout remaining = deadline;
885 if (timed_wait)
886 {
887 const Timeout now = static_cast<Timeout>(GetTicks());
888 remaining = (now >= deadline ? NO_WAIT : (deadline - now));
889 }
890
891 result = m_cv_not_full.WaitEx(cs_, remaining);
892 if (result != WAIT_RESULT_SIGNAL)
893 {
894 // timeout, cancellation or kernel failure: nothing is written
895 break;
896 }
897 }
898
899 if (result == WAIT_RESULT_SIGNAL)
900 {
901 m_buffer[m_head] = data;
902 m_head = (m_head + 1U) % N;
903 m_count += 1U;
904
905 // notify consumer that data is ready
906 m_cv_not_empty.NotifyOne_CS();
907 }
908
909 return result;
910 }
911
922 bool Write(const T &data, Timeout timeout_ticks = WAIT_INFINITE)
923 {
924 return (WriteEx(data, timeout_ticks) == WAIT_RESULT_SIGNAL);
925 }
926
934 bool TryWrite(const T &data) { return Write(data, NO_WAIT); }
935
952 EWaitResult WriteBulkEx(const T *src, size_t count, size_t &written,
953 Timeout timeout_ticks = WAIT_INFINITE)
954 {
955 written = 0U;
956
957 // null source is rejected, an empty request is trivially satisfied
958 EWaitResult result = ((src != nullptr) ? WAIT_RESULT_SIGNAL : WAIT_RESULT_FAIL);
959
960 if ((src != nullptr) && (count != 0U))
961 {
962 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
963
964 // capture an absolute deadline once, before entering the wait loop,
965 // preventing the timeout from resetting on intermediate partial writes
966 const Timeout deadline = (timed_wait ?
967 static_cast<Timeout>(GetTicks() + timeout_ticks) : timeout_ticks);
968
970
971 while (written < count)
972 {
973 // wait until there is at least 1 free slot
974 while (m_count == N)
975 {
976 Timeout remaining = deadline;
977 if (timed_wait)
978 {
979 const Timeout now = static_cast<Timeout>(GetTicks());
980 remaining = (now >= deadline ? NO_WAIT : (deadline - now));
981 }
982
983 const EWaitResult wr = m_cv_not_full.WaitEx(cs_, remaining);
984 if (wr != WAIT_RESULT_SIGNAL)
985 {
986 result = wr;
987 break; // break inner condition variable loop
988 }
989 }
990
991 // if the wait failed (timeout, cancellation), drop out of the chunk processing loop
992 if (result != WAIT_RESULT_SIGNAL)
993 {
994 break;
995 }
996
997 // calculate how many we can copy in this contiguous stretch
998 const size_t available = N - m_count;
999 const size_t to_write = ((count - written) < available) ? (count - written) : available;
1000
1001 // copy from source
1002 // note: if value type is not scalar or queue is small we copy with a for loop,
1003 // otherwise using faster memcpy version for large scalar arrays
1004 if (!std::is_scalar<T>::value || (N < 8U))
1005 {
1006 for (size_t i = 0U; i < to_write; ++i)
1007 {
1008 m_buffer[m_head] = src[written++];
1009 m_head = (m_head + 1U) % N;
1010 m_count += 1U;
1011 }
1012 }
1013 else
1014 {
1015 const size_t first_part = N - m_head;
1016
1017 if (to_write <= first_part)
1018 {
1019 STK_MEMCPY(&m_buffer[m_head], &src[written], to_write * sizeof(T));
1020 }
1021 else
1022 {
1023 STK_MEMCPY(&m_buffer[m_head], &src[written], first_part * sizeof(T));
1024 STK_MEMCPY(&m_buffer[0U], &src[written + first_part], (to_write - first_part) * sizeof(T));
1025 }
1026
1027 written += to_write;
1028 m_head = (m_head + to_write) % N;
1029 m_count += to_write;
1030 }
1031
1032 // notify consumers that data is ready
1033 m_cv_not_empty.NotifyAll_CS();
1034 }
1035 }
1036
1037 return result;
1038 }
1039
1061 size_t WriteBulk(const T *src, size_t count, Timeout timeout_ticks = WAIT_INFINITE)
1062 {
1063 size_t written = 0U;
1064 const EWaitResult result = WriteBulkEx(src, count, written, timeout_ticks);
1065 STK_UNUSED(result);
1066 return written;
1067 }
1068
1077 size_t TryWriteBulk(const T *src, size_t count) { return WriteBulk(src, count, NO_WAIT); }
1078
1091 EWaitResult ReadEx(T &data, Timeout timeout_ticks = WAIT_INFINITE)
1092 {
1095
1096 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
1097
1098 // capture an absolute deadline once, before entering the wait loop; a different
1099 // consumer can steal the just-produced element between NotifyOne_CS() and this
1100 // task re-acquiring the critical section (Mesa-style wakeup, not a direct handoff),
1101 // so re-passing the original timeout_ticks on each retry would silently restart it
1102 const Timeout deadline = (timed_wait ?
1103 static_cast<Timeout>(GetTicks() + timeout_ticks) : timeout_ticks);
1104
1105 while (m_count == 0U)
1106 {
1107 Timeout remaining = deadline;
1108 if (timed_wait)
1109 {
1110 const Timeout now = static_cast<Timeout>(GetTicks());
1111 remaining = (now >= deadline ? NO_WAIT : (deadline - now));
1112 }
1113
1114 result = m_cv_not_empty.WaitEx(cs_, remaining);
1115 if (result != WAIT_RESULT_SIGNAL)
1116 {
1117 // timeout, cancellation or kernel failure: nothing is read
1118 break;
1119 }
1120 }
1121
1122 if (result == WAIT_RESULT_SIGNAL)
1123 {
1124 data = m_buffer[m_tail];
1125 m_tail = (m_tail + 1U) % N;
1126 m_count -= 1U;
1127
1128 // notify producer that space is available
1129 m_cv_not_full.NotifyOne_CS();
1130 }
1131
1132 return result;
1133 }
1134
1145 bool Read(T &data, Timeout timeout_ticks = WAIT_INFINITE)
1146 {
1147 return (ReadEx(data, timeout_ticks) == WAIT_RESULT_SIGNAL);
1148 }
1149
1157 bool TryRead(T &data) { return Read(data, NO_WAIT); }
1158
1175 EWaitResult ReadBulkEx(T *dst, size_t count, size_t &read_count,
1176 Timeout timeout_ticks = WAIT_INFINITE)
1177 {
1178 read_count = 0U;
1179
1180 // null destination is rejected, an empty request is trivially satisfied
1181 EWaitResult result = ((dst != nullptr) ? WAIT_RESULT_SIGNAL : WAIT_RESULT_FAIL);
1182
1183 if ((dst != nullptr) && (count != 0U))
1184 {
1185 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
1186
1187 // capture an absolute deadline once, before entering the wait loop,
1188 // preventing the timeout from resetting on intermediate partial reads
1189 const Timeout deadline = (timed_wait ?
1190 static_cast<Timeout>(GetTicks() + timeout_ticks) : timeout_ticks);
1191
1193
1194 while (read_count < count)
1195 {
1196 // wait until there is at least 1 element available
1197 while (m_count == 0U)
1198 {
1199 Timeout remaining = deadline;
1200 if (timed_wait)
1201 {
1202 const Timeout now = static_cast<Timeout>(GetTicks());
1203 remaining = (now >= deadline ? NO_WAIT : (deadline - now));
1204 }
1205
1206 const EWaitResult wr = m_cv_not_empty.WaitEx(cs_, remaining);
1207 if (wr != WAIT_RESULT_SIGNAL)
1208 {
1209 result = wr;
1210 break; // break inner condition variable loop
1211 }
1212 }
1213
1214 // if the wait failed (timeout, cancellation), drop out of the chunk processing loop
1215 if (result != WAIT_RESULT_SIGNAL)
1216 {
1217 break;
1218 }
1219
1220 // determine how many we can pull in this stretch
1221 const size_t to_read = (count - read_count) < m_count ? (count - read_count) : m_count;
1222
1223 // note: if value type is not scalar or queue is small we copy with a for loop,
1224 // otherwise using faster memcpy version for large scalar arrays
1225 if (!std::is_scalar<T>::value || (N < 8U))
1226 {
1227 for (size_t i = 0U; i < to_read; ++i)
1228 {
1229 dst[read_count++] = m_buffer[m_tail];
1230 m_tail = (m_tail + 1U) % N;
1231 m_count -= 1U;
1232 }
1233 }
1234 else
1235 {
1236 const size_t first_part = N - m_tail;
1237
1238 if (to_read <= first_part)
1239 {
1240 STK_MEMCPY(&dst[read_count], &m_buffer[m_tail], to_read * sizeof(T));
1241 }
1242 else
1243 {
1244 STK_MEMCPY(&dst[read_count], &m_buffer[m_tail], first_part * sizeof(T));
1245 STK_MEMCPY(&dst[read_count + first_part], &m_buffer[0U], (to_read - first_part) * sizeof(T));
1246 }
1247
1248 read_count += to_read;
1249 m_tail = (m_tail + to_read) % N;
1250 m_count -= to_read;
1251 }
1252
1253 // notify producers that space is now available
1254 m_cv_not_full.NotifyAll_CS();
1255 }
1256 }
1257
1258 return result;
1259 }
1260
1283 size_t ReadBulk(T *dst, size_t count, Timeout timeout_ticks = WAIT_INFINITE)
1284 {
1285 size_t read_count = 0U;
1286 const EWaitResult result = ReadBulkEx(dst, count, read_count, timeout_ticks);
1287 STK_UNUSED(result);
1288 return read_count;
1289 }
1290
1298 size_t TryReadBulk(T *dst, size_t count) { return ReadBulk(dst, count, NO_WAIT); }
1299
1307 void Reset()
1308 {
1309 const ScopedCriticalSection cs_;
1310
1311 m_count = 0U;
1312 m_head = 0U;
1313 m_tail = 0U;
1314
1315 // wake all blocked producers: the pipe is now entirely empty, every slot is free
1316 // note: we do not release readers here
1317 m_cv_not_full.NotifyAll_CS();
1318 }
1319
1324 size_t GetCapacity() const { return N; }
1325
1331 size_t GetCount() const { return m_count; }
1332
1337 size_t GetSpace() const { return (N - m_count); }
1338
1344 bool IsEmpty() const { return (m_count == 0U); }
1345
1351 bool IsFull() const { return (m_count == N); }
1352
1353private:
1355
1357 size_t m_head;
1358 size_t m_tail;
1359 size_t m_count;
1362};
1363
1364} // namespace sync
1365} // namespace stk
1366
1367#endif /* STK_SYNC_PIPE_H_ */
static void STK_MEMCPY(void *const dest, const void *const src, const size_t size)
Implementation of STK_MEMCPY.
Definition stk_arch.h:731
#define STK_UNUSED(X)
Explicitly marks a variable as unused to suppress compiler warnings.
Definition stk_defs.h:715
#define STK_NONCOPYABLE_CLASS(TYPE)
Disables copy construction and assignment for a class.
Definition stk_defs.h:708
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:516
#define STK_VIRT_DTOR
Makes destructors virtual and compliant to strict rules if STK_STRICT_COMPLIANCY=0.
Definition stk_defs.h:261
Implementation of synchronization primitive: stk::sync::ConditionVariable.
Namespace of STK package.
static constexpr Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:220
EWaitResult
Wait result (see IKernelService::Wait).
Definition stk_common.h:123
@ WAIT_RESULT_FAIL
IKernelService::Wait returned with error without waiting.
Definition stk_common.h:124
@ WAIT_RESULT_SIGNAL
The wake was caused by a signal.
Definition stk_common.h:125
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:156
static Ticks GetTicks()
Get number of ticks elapsed since kernel start.
Definition stk_helper.h:438
static constexpr Timeout WAIT_INFINITE
Timeout value: block indefinitely until the synchronization object is signaled.
Definition stk_common.h:214
static constexpr T Min(T a, T b) noexcept
Compile-time minimum of two values.
Definition stk_defs.h:753
Synchronization primitives for task coordination and resource protection.
Traceable object.
Definition stk_common.h:533
RAII-style low-level synchronization primitive for atomic code execution. Used as building brick for ...
Definition stk_sync_cs.h:54
Condition Variable primitive for signaling between tasks based on specific predicates.
Definition stk_sync_cv.h:68
size_t WriteBulk(const void *src, size_t count, Timeout timeout_ticks=WAIT_INFINITE)
Write multiple elements to the pipe.
bool TryRead(void *data)
Attempt to read a single element from the pipe without blocking.
const size_t m_capacity
maximum number of elements stored in the pipe
~Pipe()=default
Destructor.
size_t TryReadBulkTriggered(void *dst, size_t max_count)
Non-blocking variant of ReadBulkTriggered.
void Reset()
Discard all elements and reset the pipe to the empty state.
EWaitResult WriteBulkEx(const void *src, size_t count, size_t &written, Timeout timeout_ticks=WAIT_INFINITE)
Write multiple elements to the pipe, preserving the full wait outcome.
bool Read(void *data, Timeout timeout_ticks=WAIT_INFINITE)
Read a single element from the pipe.
EWaitResult ReadBulkTriggeredEx(void *dst, size_t trigger, size_t max_count, size_t &read_count, Timeout timeout_ticks=WAIT_INFINITE)
Read at least trigger elements, then drain up to max_count without blocking, preserving the full wait...
EWaitResult ReadBulkEx(void *dst, size_t count, size_t &read_count, Timeout timeout_ticks=WAIT_INFINITE)
Read multiple elements from the pipe, preserving the full wait outcome.
EWaitResult ReadEx(void *data, Timeout timeout_ticks=WAIT_INFINITE)
Read a single element from the pipe, preserving the full wait outcome.
size_t ReadBulk(void *dst, size_t count, Timeout timeout_ticks=WAIT_INFINITE)
Read multiple elements from the pipe.
uint8_t * Slot(size_t idx) const
ConditionVariable m_cv_not_full
signaled by Read()/Reset() when the pipe is no longer full
bool Write(const void *data, Timeout timeout_ticks=WAIT_INFINITE)
Write a single element to the pipe.
size_t GetElementSize() const
Get the size of each element in bytes.
size_t m_head
write index (next slot to be written by Write())
size_t TryWriteBulk(const void *src, size_t count)
Attempt to write multiple elements to the pipe without blocking.
bool IsEmpty() const
Check if the pipe is currently empty.
EWaitResult WriteEx(const void *data, Timeout timeout_ticks=WAIT_INFINITE)
Write a single element to the pipe, preserving the full wait outcome.
size_t GetCapacity() const
Get the maximum number of elements the pipe can hold.
size_t Next(size_t idx) const
bool TryWrite(const void *data)
Attempt to write a single element to the pipe without blocking.
size_t GetCount() const
Get the current number of elements in the pipe.
ConditionVariable m_cv_not_empty
signaled by Write() when the pipe transitions from empty
bool IsFull() const
Check if the pipe is currently full.
size_t TryReadBulk(void *dst, size_t count)
Attempt to read multiple elements from the pipe without blocking.
const size_t m_element_size
size of each element in bytes
static const size_t CAPACITY_MAX
Max capacity supported (number of elements).
Pipe(uint8_t *buf, size_t capacity, size_t element_size)
Constructor.
size_t m_tail
read index (next slot to be read by Read())
uint8_t * m_buffer
flat byte ring-buffer: capacity slots of element_size bytes each
size_t DrainLocked(uint8_t *dst_bytes, size_t count)
size_t GetSpace() const
Get the number of free slots currently available.
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.
bool IsStorageValid() const
Verify that the backing storage is valid and the pipe is ready for use.
uint8_t * GetBuffer()
Get a pointer to the raw backing buffer.
size_t m_count
current number of elements stored in the pipe
EWaitResult ReadBulkEx(T *dst, size_t count, size_t &read_count, Timeout timeout_ticks=WAIT_INFINITE)
Read multiple elements from the pipe, preserving the full wait outcome.
size_t TryReadBulk(T *dst, size_t count)
Attempt to read multiple elements from the pipe without blocking.
EWaitResult WriteBulkEx(const T *src, size_t count, size_t &written, Timeout timeout_ticks=WAIT_INFINITE)
Write multiple elements to the pipe, preserving the full wait outcome.
size_t GetCount() const
Get the current number of elements in the pipe.
bool IsEmpty() const
Check if the pipe is currently empty.
size_t ReadBulk(T *dst, size_t count, Timeout timeout_ticks=WAIT_INFINITE)
Read multiple elements from the pipe.
bool Write(const T &data, Timeout timeout_ticks=WAIT_INFINITE)
Write data to the pipe.
bool TryRead(T &data)
Attempt to read data from the pipe without blocking.
EWaitResult WriteEx(const T &data, Timeout timeout_ticks=WAIT_INFINITE)
Write data to the pipe, preserving the full wait outcome.
PipeT()
Constructor.
bool Read(T &data, Timeout timeout_ticks=WAIT_INFINITE)
Read data from the pipe.
EWaitResult ReadEx(T &data, Timeout timeout_ticks=WAIT_INFINITE)
Read data from the pipe, preserving the full wait outcome.
bool TryWrite(const T &data)
Attempt to write data to the pipe without blocking.
size_t TryWriteBulk(const T *src, size_t count)
Attempt to write multiple elements to the pipe without blocking.
size_t WriteBulk(const T *src, size_t count, Timeout timeout_ticks=WAIT_INFINITE)
Write multiple elements to the pipe.
void Reset()
Discard all elements and reset the pipe to the empty state.
bool IsFull() const
Check if the pipe is currently full.
size_t GetSpace() const
Get the number of free slots currently available.
size_t GetCapacity() const
Get the maximum number of elements the pipe can hold.