From 048e89f022b2d7f43bce553b052819f8027af322 Mon Sep 17 00:00:00 2001 From: Julien Hecht Date: Wed, 22 Jul 2026 15:52:42 +0200 Subject: [PATCH] Add etl::counting_semaphore and etl::binary_semaphore --- include/etl/file_error_numbers.h | 1 + include/etl/semaphore.h | 60 +++++++++ include/etl/semaphore/semaphore_clang_sync.h | 34 +++++ include/etl/semaphore/semaphore_exceptions.h | 71 +++++++++++ include/etl/semaphore/semaphore_freertos.h | 126 +++++++++++++++++++ include/etl/semaphore/semaphore_gcc_sync.h | 117 +++++++++++++++++ include/etl/semaphore/semaphore_std.h | 48 +++++++ 7 files changed, 457 insertions(+) create mode 100644 include/etl/semaphore.h create mode 100644 include/etl/semaphore/semaphore_clang_sync.h create mode 100644 include/etl/semaphore/semaphore_exceptions.h create mode 100644 include/etl/semaphore/semaphore_freertos.h create mode 100644 include/etl/semaphore/semaphore_gcc_sync.h create mode 100644 include/etl/semaphore/semaphore_std.h diff --git a/include/etl/file_error_numbers.h b/include/etl/file_error_numbers.h index 945f1d87..fed24366 100644 --- a/include/etl/file_error_numbers.h +++ b/include/etl/file_error_numbers.h @@ -112,4 +112,5 @@ SOFTWARE. #define ETL_FORMAT_FILE_ID "79" #define ETL_INPLACE_FUNCTION_FILE_ID "80" #define ETL_INTRUSIVE_AVL_TREE_FILE_ID "81" +#define ETL_SEMAPHORE_FILE_ID "82" #endif diff --git a/include/etl/semaphore.h b/include/etl/semaphore.h new file mode 100644 index 00000000..dfcb90ae --- /dev/null +++ b/include/etl/semaphore.h @@ -0,0 +1,60 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SEMAPHORE_INCLUDED +#define ETL_SEMAPHORE_INCLUDED + +#include "platform.h" + +#include "semaphore/semaphore_exceptions.h" + +#if defined(ETL_TARGET_OS_FREERTOS) + #include "semaphore/semaphore_freertos.h" + #define ETL_HAS_SEMAPHORE 1 +#elif ETL_USING_STL && ETL_USING_CPP20 && ETL_USING_STD_SEMAPHORE + #include "semaphore/semaphore_std.h" + #define ETL_HAS_SEMAPHORE 1 +#elif defined(ETL_COMPILER_GCC) && ETL_USING_CPP11 + #include "semaphore/semaphore_gcc_sync.h" + #define ETL_HAS_SEMAPHORE 1 +#elif defined(ETL_COMPILER_CLANG) && ETL_USING_CPP11 + #include "semaphore/semaphore_clang_sync.h" + #define ETL_HAS_SEMAPHORE 1 +#else + #define ETL_HAS_SEMAPHORE 0 +#endif + +namespace etl +{ + namespace traits + { + static ETL_CONSTANT bool has_semaphore = (ETL_HAS_SEMAPHORE == 1); + } +} // namespace etl + +#endif diff --git a/include/etl/semaphore/semaphore_clang_sync.h b/include/etl/semaphore/semaphore_clang_sync.h new file mode 100644 index 00000000..0c7e88d0 --- /dev/null +++ b/include/etl/semaphore/semaphore_clang_sync.h @@ -0,0 +1,34 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SEMAPHORE_CLANG_SYNC_INCLUDED +#define ETL_SEMAPHORE_CLANG_SYNC_INCLUDED + +#include "semaphore_gcc_sync.h" + +#endif diff --git a/include/etl/semaphore/semaphore_exceptions.h b/include/etl/semaphore/semaphore_exceptions.h new file mode 100644 index 00000000..23b7b007 --- /dev/null +++ b/include/etl/semaphore/semaphore_exceptions.h @@ -0,0 +1,71 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SEMAPHORE_EXCEPTIONS_INCLUDED +#define ETL_SEMAPHORE_EXCEPTIONS_INCLUDED + +#include "../platform.h" +#include "../error_handler.h" +#include "../exception.h" + +namespace etl +{ + + //*************************************************************************** + ///\ingroup semaphore + /// Exception base for semaphores + //*************************************************************************** + class semaphore_exception : public exception + { + public: + + semaphore_exception(string_type reason_, string_type file_name_, numeric_type line_number_) + : exception(reason_, file_name_, line_number_) + { + } + }; + + //*************************************************************************** + ///\ingroup semaphore + /// Exception thrown when the semaphore value gets out of bounds + /// For example: + /// - Creating a semaphore with a `desired` value over `max()` + /// - Releasing a semaphore with a negative update value, or value that would bring it over `max()` + //*************************************************************************** + class semaphore_out_of_bounds : public semaphore_exception + { + public: + + semaphore_out_of_bounds(string_type file_name_, numeric_type line_number_) + : semaphore_exception(ETL_ERROR_TEXT("semaphore: out of bounds", ETL_SEMAPHORE_FILE_ID"A"), file_name_, line_number_) + { + } + }; +} // namespace etl + +#endif diff --git a/include/etl/semaphore/semaphore_freertos.h b/include/etl/semaphore/semaphore_freertos.h new file mode 100644 index 00000000..9a89e133 --- /dev/null +++ b/include/etl/semaphore/semaphore_freertos.h @@ -0,0 +1,126 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SEMAPHORE_FREERTOS_INCLUDED +#define ETL_SEMAPHORE_FREERTOS_INCLUDED + +#include "../platform.h" +#include "../chrono.h" +#include "../error_handler.h" +#include "../limits.h" +#include "semaphore_exceptions.h" + +namespace etl +{ + //*************************************************************************** + ///\ingroup semaphore + ///\brief This counting_semaphore class is implemented using FreeRTOS's semaphores + //*************************************************************************** + template ::max()> + class counting_semaphore + { + // On FreeRTOS, LeastMaxValue = 0 causes an assert. This deviates slightly from the C++ spec, but it should be fine. + ETL_STATIC_ASSERT(LeastMaxValue > 0, "LeastMaxValue should be 1 or more"); + ETL_STATIC_ASSERT(LeastMaxValue <= numeric_limits::max(), "LeastMaxValue is too high"); + + public: + + explicit counting_semaphore(ptrdiff_t desired) + { + ETL_ASSERT(desired >= 0, ETL_ERROR(semaphore_out_of_bounds)); + sem = xSemaphoreCreateCountingStatic(max(), desired, &semaphore_allocation); + } + + void acquire() + { + // portMAX_DELAY=block forever + // Looping guarantees waiting forever even if the configuration does not have `INCLUDE_vTaskSuspend == 1` + while (xSemaphoreTake(sem, portMAX_DELAY) != pdTRUE); + } + + bool try_acquire() + { + return try_acquire_for(0_s); + } + + template + bool try_acquire_for(const chrono::duration& rel_time) + { + using URep_t = make_unsigned_t; + + TickType_t ticks_to_wait = 0; + chrono::milliseconds ms_to_wait{rel_time}; + if (ms_to_wait > chrono::duration::zero()) + { + ticks_to_wait = divide_round_to_ceiling(static_cast(ms_to_wait.count()), portTICK_PERIOD_MS); + } + return xSemaphoreTake(sem, ticks_to_wait) == pdTRUE; + } + + template + bool try_acquire_until(const chrono::time_point& abs_time) + { + return try_acquire_for(abs_time - Clock::now()); + } + + void release(ptrdiff_t update = 1) + { + if (update <= 0) + { + return; + } + + // No need to check if the semaphore goes over `max()` because it's guaranteed by FreeRTOS APIs + while (update--) + { + xSemaphoreGive(sem); + } + } + + static constexpr ptrdiff_t max() noexcept + { + return LeastMaxValue; + } + + private: + + // Non-copyable + counting_semaphore(const counting_semaphore&) ETL_DELETE; + counting_semaphore& operator=(const counting_semaphore&) ETL_DELETE; + + // Memory to hold the semaphore + StaticSemaphore_t semaphore_allocation; + + // The semaphore handle itself + SemaphoreHandle_t sem; + }; + + using binary_semaphore = counting_semaphore<1>; +} // namespace etl + +#endif diff --git a/include/etl/semaphore/semaphore_gcc_sync.h b/include/etl/semaphore/semaphore_gcc_sync.h new file mode 100644 index 00000000..a9b1e8ae --- /dev/null +++ b/include/etl/semaphore/semaphore_gcc_sync.h @@ -0,0 +1,117 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SEMAPHORE_GCC_SYNC_INCLUDED +#define ETL_SEMAPHORE_GCC_SYNC_INCLUDED + +#include "../platform.h" +#include "../chrono.h" +#include "../limits.h" +#include "semaphore_exceptions.h" + +namespace etl +{ + //*************************************************************************** + ///\ingroup semaphore + ///\brief This counting_semaphore class is implemented using GCC's __atomic functions. + //*************************************************************************** + template ::max()> + class counting_semaphore + { + public: + + ETL_STATIC_ASSERT(LeastMaxValue >= 0, "LeastMaxValue shall be non-negative"); + ETL_STATIC_ASSERT(LeastMaxValue <= numeric_limits::max(), "LeastMaxValue is too high"); + + ETL_CONSTEXPR14 explicit counting_semaphore(ptrdiff_t desired) + : counter(desired) + { + ETL_ASSERT(desired >= 0 && desired <= max(), ETL_ERROR(semaphore_out_of_bounds)); + } + + void acquire() + { + while (!try_acquire()) + { + } + } + + bool try_acquire() + { + ptrdiff_t old = __atomic_load_n(&counter, __ATOMIC_ACQUIRE); + if (old <= 0) + { + return false; + } + + return __atomic_compare_exchange_n(&counter, &old, old - 1, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED); + } + + template + bool try_acquire_for(const chrono::duration& rel_time) + { + auto now = chrono::steady_clock::now(); + return try_acquire_until(now + rel_time); + } + + template + bool try_acquire_until(const chrono::time_point& abs_time) + { + bool ret = false; + while (!(ret = try_acquire()) && Clock::now() < abs_time) + { + } + return ret; + } + + void release(ptrdiff_t update = 1) + { + ptrdiff_t old = __atomic_fetch_add(&counter, update, __ATOMIC_RELEASE); + + // Preconditions are tested afterwards because otherwise it makes the atomic code needlessly complex. + ETL_ASSERT(update >= 0, ETL_ERROR(semaphore_out_of_bounds)); + ETL_ASSERT(update <= max() - old, ETL_ERROR(semaphore_out_of_bounds)); + } + + static constexpr ptrdiff_t max() noexcept + { + return LeastMaxValue; + } + + private: + + counting_semaphore(const counting_semaphore&) ETL_DELETE; + counting_semaphore& operator=(const counting_semaphore&) ETL_DELETE; + + ptrdiff_t counter; + }; + + using binary_semaphore = counting_semaphore<1>; +} // namespace etl + +#endif diff --git a/include/etl/semaphore/semaphore_std.h b/include/etl/semaphore/semaphore_std.h new file mode 100644 index 00000000..74aaf2e5 --- /dev/null +++ b/include/etl/semaphore/semaphore_std.h @@ -0,0 +1,48 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SEMAPHORE_STD_INCLUDED +#define ETL_SEMAPHORE_STD_INCLUDED + +#include "../platform.h" +#include "../limits.h" + +#include + +namespace etl +{ + //*************************************************************************** + ///\ingroup semaphore + ///\brief These semaphore classes are implemented using std::counting_semaphore and std::binary_semaphore. + //*************************************************************************** + template ::max()> + using counting_semaphore = std::counting_semaphore; + using binary_semaphore = std::binary_semaphore; +} // namespace etl + +#endif