docs: add OS abstraction layers doc
Adds a new top level doc on Pigweed's OS abstraction layers.
While doing so, also adds some minor updates to the relevant modules.
Change-Id: Ie5c324088d06a1b4c6c11ff5abbbea03f4e9c61d
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/44920
Commit-Queue: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index 0c99c11..26004b7 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -37,6 +37,7 @@
"faq.rst",
"getting_started.rst",
"module_structure.rst",
+ "os_abstraction_layers.rst",
"style_guide.rst",
]
}
diff --git a/docs/index.rst b/docs/index.rst
index 8d996e4..28cf4ab 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -15,6 +15,7 @@
docs/code_of_conduct
docs/embedded_cpp_guide
Code Style <docs/style_guide>
+ docs/os_abstraction_layers
targets
Build System <build_system>
FAQ <docs/faq>
diff --git a/docs/os_abstraction_layers.rst b/docs/os_abstraction_layers.rst
new file mode 100644
index 0000000..08a6db6
--- /dev/null
+++ b/docs/os_abstraction_layers.rst
@@ -0,0 +1,493 @@
+.. _docs-os_abstraction_layers:
+
+=====================
+OS Abstraction Layers
+=====================
+Pigweed’s operating system abstraction layers are configurable building blocks.
+They are designed to be lightweight, portable, and easy to use while giving
+users full control and configurability.
+
+Although we primarily target smaller-footprint MMU-less 32-bit microcontrollers,
+the OS abstraction layers are written to work on everything from single-core
+bare metal low end microcontrollers to asymmetric multiprocessing (AMP) and
+symmetric multiprocessing (SMP) embedded systems using Real Time Operating
+Systems (RTOS). They even fully work on your developer workstation on Linux,
+Windows, or MacOS!
+
+.. list-table::
+
+ * - **Environment**
+ - **Status**
+ * - FreeRTOS
+ - Supported
+ * - ThreadX
+ - Supported
+ * - embOS
+ - *In Progress*
+ * - STL
+ - Supported
+ * - Zephyr
+ - Planned
+ * - CMSIS-RTOS API v2 & RTX5
+ - Planned
+ * - Baremetal
+ - *In Progress*
+
+.. contents::
+ :local:
+ :depth: 1
+
+-------------
+OS Primitives
+-------------
+Pigweed's OS abstraction layers are divided by the functional grouping of the
+primitives. Many of our APIs are similar or nearly identical to C++'s Standard
+Template Library (STL) with the notable exception that we do not support
+exceptions. We opted to follow the STL's APIs partially because they are
+relatively well thought out and many developers are already familiar with them,
+but also because this means they are compatible with existing helpers in the STL
+which can then be further leveraged.
+
+---------------------------
+pw_chrono - Time Primitives
+---------------------------
+The :ref:`module-pw_chrono` module provides the building blocks for expressing
+durations, timestamps, and acquiring the current time. This in turn is used by
+other modules, including :ref:`module-pw_sync` and :ref:`module-pw_thread` as
+the basis for any time bound APIs (i.e. with timeouts and/or deadlines). Note
+that this module is optional and bare metal targets may opt not to use this.
+
+.. list-table::
+
+ * - **Supported On**
+ - **SystemClock**
+ * - FreeRTOS
+ - :ref:`module-pw_chrono_freertos`
+ * - ThreadX
+ - :ref:`module-pw_chrono_threadx`
+ * - embOS
+ - :ref:`module-pw_chrono_embos`
+ * - STL
+ - :ref:`module-pw_chrono_stl`
+ * - Zephyr
+ - Planned
+ * - CMSIS-RTOS API v2 & RTX5
+ - Planned
+ * - Baremetal
+ - Planned
+
+
+SystemClock
+===========
+For RTOS and HAL interactions, we provide a ``pw::chrono::SystemClock`` facade
+which provides 64 bit timestamps and duration support along with a C API. For
+C++ there is an optional virtual wrapper, ``pw::chrono::VirtualSystemClock``,
+around the singleton clock facade to enable dependency injection.
+
+.. code-block:: cpp
+
+ #include <chrono>
+
+ #include "pw_thread/sleep.h"
+
+ using namespace std::literals::chrono_literals;
+
+ void ThisSleeps() {
+ pw::thread::sleep_for(42ms);
+ }
+
+Unlike the STL's time bound templated APIs which are not specific to a
+particular clock, Pigweed's time bound APIs are strongly typed to use the
+``pw::chrono::SystemClock``'s ``duration`` and ``time_points`` directly.
+
+.. code-block:: cpp
+
+ #include "pw_chrono/system_clock.h"
+
+ bool HasThisPointInTimePassed(const SystemClock::time_point timestamp) {
+ return SystemClock::now() > timestamp;
+ }
+
+------------------------------------
+pw_sync - Synchronization Primitives
+------------------------------------
+The :ref:`module-pw_sync` provides the building blocks for synchronizing between
+threads and/or interrupts through signaling primitives and critical section lock
+primitives.
+
+Critical Section Lock Primitives
+================================
+Pigweed's locks support Clang's thread safety lock annotations and the STL's
+RAII helpers.
+
+.. list-table::
+
+ * - **Supported On**
+ - **Mutex**
+ - **TimedMutex**
+ - **InterruptSpinLock**
+ * - FreeRTOS
+ - :ref:`module-pw_sync_freertos`
+ - :ref:`module-pw_sync_freertos`
+ - :ref:`module-pw_sync_freertos`
+ * - ThreadX
+ - :ref:`module-pw_sync_threadx`
+ - :ref:`module-pw_sync_threadx`
+ - :ref:`module-pw_sync_threadx`
+ * - embOS
+ - :ref:`module-pw_sync_embos`
+ - :ref:`module-pw_sync_embos`
+ - :ref:`module-pw_sync_embos`
+ * - STL
+ - :ref:`module-pw_sync_stl`
+ - :ref:`module-pw_sync_stl`
+ - :ref:`module-pw_sync_stl`
+ * - Zephyr
+ - Planned
+ - Planned
+ - Planned
+ * - CMSIS-RTOS API v2 & RTX5
+ - Planned
+ - Planned
+ - Planned
+ * - Baremetal
+ - Planned, not ready for use
+ - ✗
+ - Planned, not ready for use
+
+
+Thread Safe Mutex
+-----------------
+The ``pw::sync::Mutex`` protects shared data from being simultaneously accessed
+by multiple threads. Optionally, the ``pw::sync::TimedMutex`` can be used as an
+extension with timeout and deadline based semantics.
+
+.. code-block:: cpp
+
+ #include <mutex>
+
+ #include "pw_sync/mutex.h"
+
+ pw::sync::Mutex mutex;
+
+ void ThreadSafeCriticalSection() {
+ std::lock_guard lock(mutex);
+ NotThreadSafeCriticalSection();
+ }
+
+Interrupt Safe InterruptSpinLock
+--------------------------------
+The ``pw::sync::InterruptSpinLock`` protects shared data from being
+simultaneously accessed by multiple threads and/or interrupts as a targeted
+global lock, with the exception of Non-Maskable Interrupts (NMIs). Unlike global
+interrupt locks, this also works safely and efficiently on SMP systems.
+
+.. code-block:: cpp
+
+ #include <mutex>
+
+ #include "pw_sync/interrupt_spin_lock.h"
+
+ pw::sync::InterruptSpinLock interrupt_spin_lock;
+
+ void InterruptSafeCriticalSection() {
+ std::lock_guard lock(interrupt_spin_lock);
+ NotThreadSafeCriticalSection();
+ }
+
+Signaling Primitives
+====================
+Native signaling primitives tend to vary more compared to critical section locks
+across different platforms. For example, although common signaling primitives
+like semaphores are in most if not all RTOSes and even POSIX, it was not in the
+STL before C++20. Likewise many C++ developers are surprised that conditional
+variables tend to not be natively supported on RTOSes. Although you can usually
+build any signaling primitive based on other native signaling primitives,
+this may come with non-trivial added overhead in ROM, RAM, and execution
+efficiency.
+
+For this reason, Pigweed intends to provide some simpler signaling primitives
+which exist to solve a narrow programming need but can be implemented as
+efficiently as possible for the platform that it is used on. This simpler but
+highly portable class of signaling primitives is intended to ensure that a
+portability efficiency tradeoff does not have to be made up front.
+
+.. list-table::
+
+ * - **Supported On**
+ - **ThreadNotification**
+ - **TimedThreadNotification**
+ - **CountingSemaphore**
+ - **BinarySemaphore**
+ * - FreeRTOS
+ - :ref:`module-pw_sync_freertos`
+ - :ref:`module-pw_sync_freertos`
+ - :ref:`module-pw_sync_freertos`
+ - :ref:`module-pw_sync_freertos`
+ * - ThreadX
+ - :ref:`module-pw_sync_threadx`
+ - :ref:`module-pw_sync_threadx`
+ - :ref:`module-pw_sync_threadx`
+ - :ref:`module-pw_sync_threadx`
+ * - embOS
+ - :ref:`module-pw_sync_embos`
+ - :ref:`module-pw_sync_embos`
+ - :ref:`module-pw_sync_embos`
+ - :ref:`module-pw_sync_embos`
+ * - STL
+ - :ref:`module-pw_sync_stl`
+ - :ref:`module-pw_sync_stl`
+ - :ref:`module-pw_sync_stl`
+ - :ref:`module-pw_sync_stl`
+ * - Zephyr
+ - Planned
+ - Planned
+ - Planned
+ - Planned
+ * - CMSIS-RTOS API v2 & RTX5
+ - Planned
+ - Planned
+ - Planned
+ - Planned
+ * - Baremetal
+ - Planned
+ - ✗
+ - TBD
+ - TBD
+
+Thread Notification
+-------------------
+Pigweed intends to provide the ``pw::sync::ThreadNotification`` and
+``pw::sync::TimedThreadNotification`` facades which permit a singler consumer to
+block until an event occurs. This should be backed by the most efficient native
+primitive for a target, regardless of whether that is a semaphore, event flag
+group, condition variable, or direct task notification with a critical section
+something else.
+
+Counting Semaphore
+------------------
+The ``pw::sync::CountingSemaphore`` is a synchronization primitive that can be
+used for counting events and/or resource management where receiver(s) can block
+on acquire until notifier(s) signal by invoking release.
+
+.. code-block:: cpp
+
+ #include "pw_sync/counting_semaphore.h"
+
+ pw::sync::CountingSemaphore event_semaphore;
+
+ void NotifyEventOccurred() {
+ event_semaphore.release();
+ }
+
+ void HandleEventsForever() {
+ while (true) {
+ event_semaphore.acquire();
+ HandleEvent();
+ }
+ }
+
+Binary Semaphore
+----------------
+The ``pw::sync::BinarySemaphore`` is a specialization of the counting semaphore
+with an arbitrary token limit of 1, meaning it's either full or empty.
+
+.. code-block:: cpp
+
+ #include "pw_sync/binary_semaphore.h"
+
+ pw::sync::BinarySemaphore do_foo_semaphore;
+
+ void NotifyResultReady() {
+ result_ready_semaphore.release();
+ }
+
+ void BlockUntilResultReady() {
+ result_ready_semaphore.acquire();
+ }
+
+--------------------------------
+pw_thread - Threading Primitives
+--------------------------------
+The :ref:`module-pw_thread` module provides the building blocks for creating and
+using threads including yielding and sleeping.
+
+.. list-table::
+
+ * - **Supported On**
+ - **Thread Creation**
+ - **Thread Id/Sleep/Yield**
+ * - FreeRTOS
+ - :ref:`module-pw_sync_freertos`
+ - :ref:`module-pw_sync_freertos`
+ * - ThreadX
+ - :ref:`module-pw_sync_threadx`
+ - :ref:`module-pw_sync_threadx`
+ * - embOS
+ - Under Development
+ - :ref:`module-pw_sync_embos`
+ * - STL
+ - :ref:`module-pw_sync_stl`
+ - :ref:`module-pw_sync_stl`
+ * - Zephyr
+ - Planned
+ - Planned
+ * - CMSIS-RTOS API v2 & RTX5
+ - Planned
+ - Planned
+ * - Baremetal
+ - ✗
+ - ✗
+
+Thread Creation
+===============
+The ``pw::thread::Thread``’s API is C++11 STL ``std::thread`` like. Unlike
+``std::thread``, the Pigweed's API requires ``pw::thread::Options`` as an
+argument for creating a thread. This is used to give the user full control over
+the native OS's threading options without getting in your way.
+
+.. code-block:: cpp
+
+ #include "pw_thread/detached_thread.h"
+ #include "pw_thread_freertos/context.h"
+ #include "pw_thread_freertos/options.h"
+
+ pw::thread::freertos::ContextWithStack<42> example_thread_context;
+
+ void StartDetachedExampleThread() {
+ pw::thread::DetachedThread(
+ pw::thread::freertos::Options()
+ .set_name("static_example_thread")
+ .set_priority(kFooPriority)
+ .set_static_context(example_thread_context),
+ example_thread_function);
+ }
+
+Controlling the current thread
+==============================
+Beyond thread creation, Pigweed offers support for sleeping, identifying, and
+yielding the current thread.
+
+.. code-block:: cpp
+
+ #include "pw_thread/yield.h"
+
+ void CooperativeBusyLooper() {
+ while (true) {
+ DoChunkOfWork();
+ pw::this_thread::yield();
+ }
+ }
+
+--------------------------------------------
+Execution Contexts & Thread-Safety API Model
+--------------------------------------------
+The explosion of real contexts is too large for Pigweed to fully cover in a way
+that provides value. First there are many more contexts than just threads and
+IRQ handlers on microcontrollers, there are many more meta contexts like
+non-blocking thread callbacks which may have scheduling and/or interrupts masked
+to some degree. On top of this some environments like in userspace may not even
+have interrupts and instead deal with signals. Instead we use the following
+simplified execution thread-safety model which our APIs should be ported to
+support regardless of the real contexts they are executed in:
+
+**Thread Safe APIs** - These APIs are safe to use in any execution context where
+one can use blocking or yielding APIs such as sleeping, blocking on a mutex
+waiting on a semaphore.
+
+**Interrupt (IRQ) Safe APIs** - These APIs can be used in any execution context
+which cannot use blocking and yielding APIs. These APIs must protect themselves
+from preemption from maskable interrupts, etc. This includes critical section
+thread contexts in addition to "real" interrupt contexts. Our definition
+explicitly excludes any interrupts which are not masked when holding a SpinLock,
+those are all considered non-maskable interrupts. An interrupt safe API may
+always be safely used in a context which permits thread safe APIs.
+
+**Non-Maskable Interrupt (NMI) Safe APIs** - Like the Interrupt Safe APIs, these
+can be used in any execution context which cannot use blocking or yielding APIs.
+In addition, these may be used by interrupts which are not masked when for
+example holding a SpinLock like CPU exceptions or C++/POSIX signals. These tend
+to come with significant overhead and restrictions compared to regular interrupt
+safe APIs as they cannot rely on critical sections for implementations, instead
+only atomic signaling can be used. An interrupt safe API may always be safely
+used in a context which permits interrupt safe and thread safe APIs.
+
+Instead of going with context specific APIs, e.g. FreeRTOS's ``*FromISR()``
+APIs, Pigweed opted to go with the merged (context agnostic) API which validates
+the context requirements through ``DASSERT`` and ``DCHECK`` in the backends
+(user configurable). We did this primarily for two reasons. The explosion of
+real contexts is too large for Pigweed to fully cover as mentioned above,
+meaning there would likely have to be some context aware multiplexing with our
+simplified thread safety model split APIs. Second, we would recommend a
+``DHCECK`` to enforce context requirements regardless, so we've opted with a
+simplest API which also happens to match both the C++'s STL and Google's Abseil
+relatively closely.
+
+---------------------------------------------------
+Construction Requirements & Initialization Paradigm
+---------------------------------------------------
+
+**TL;DR: Pigweed OS primitives are initialized through C++ construction.**
+
+We have chosen to go with a model which initializes the synchronization
+primitive during C++ object construction. This means that there is a requirement
+in order for static instantiation to be safe that the user ensures that any
+necessary kernel and/or platform initialization is done before the global static
+constructors are run which would include construction of the C++ synchronization
+primitives.
+
+In addition this model for now assumes that Pigweed code will always be used to
+construct synchronization primitives used with Pigweed modules. Note that with
+this model the backend provider can decide if they want to statically
+preallocate space for the primitives or rely on dynamic allocation strategies.
+If we discover at a later point that this is not sufficiently portable than we
+can either produce an optional constructor that takes in a reference to an
+existing native synchronization type and wastes a little bit RAM or we can
+refactor the existing class into two layers where one is a StaticMutex for
+example and the other is a Mutex which only holds a handle to the native mutex
+type. This would then permit users who cannot construct their synchronization
+primitives to skip the optional static layer.
+
+Kernel / Platform Initialization Before C++ Global Static Constructors
+======================================================================
+What is this kernel and/or platform initialization that must be done first?
+
+It's not uncommon for an RTOS to require some initialization functions to be
+invoked before more of its API can be safely used. For example for CMSIS RTOSv2
+``osKernelInitialize()`` must be invoked before anything but two basic getters
+are called. Similarly, Segger's embOS requires ``OS_Init()`` to be invoked first
+before any other embOS API.
+
+.. Note::
+ To get around this one should invoke these initialization functions earlier
+ and/or delay the static C++ constructors to meet this ordering requirement. As
+ an example if you were using :ref:`module-pw_boot_armv7m`, then
+ ``pw_boot_PreStaticConstructorInit()`` would be a great place to invoke kernel
+ initialization.
+
+-------
+Roadmap
+-------
+Pigweed is still actively expanding and improving its OS Abstraction Layers.
+That being said, the following concrete areas are being worked on and can be
+expected to land at some point in the future:
+
+1. Thread creation support for embOS is in progress.
+2. We'd like to offer a system clock based timer abstraction facade which can be
+ used on either an RTOS or a hardware timer.
+3. We are evaluating a less-portable but very useful portability facade for
+ event flags / groups. This would make it even easier to ensure all firmware
+ can be fully executed on the host.
+4. Cooperative cancellation thread joining along with a ``std::jhtread`` like
+ wrapper is in progress.
+5. We'd like to add support for queues, message queues, and similar channel
+ abstractions which also support interprocessor communication in a transparent
+ manner.
+6. We're interested in supporting asynchronous worker queues and worker queue
+ pools.
+7. Migrate HAL and similar APIs to use deadlines for the backend virtual
+ interfaces to permit a smaller vtable which supports both public timeout and
+ deadline semantics.
+8. Baremetal support is partially in place today, but it's not ready for use.
+9. Most of our APIs today are focused around synchronous blocking APIs, however
+ we would love to extend this to include asynchronous APIs.
diff --git a/pw_chrono/docs.rst b/pw_chrono/docs.rst
index e62aba6..62cc03b 100644
--- a/pw_chrono/docs.rst
+++ b/pw_chrono/docs.rst
@@ -7,9 +7,20 @@
leveraging many pieces of STL's the ``std::chrono`` library but with a focus
on portability for constrained embedded devices and maintaining correctness.
+At a high level Pigweed's time primitives rely on C++'s
+`<chrono> <https://en.cppreference.com/w/cpp/header/chrono>`_ library to enable
+users to express intents with strongly typed real time units. In addition, it
+extends the C++ named
+`Clock <https://en.cppreference.com/w/cpp/named_req/Clock>`_ and
+`TrivialClock <https://en.cppreference.com/w/cpp/named_req/TrivialClock>`_
+requirements with additional attributes such as whether a clock is monotonic
+(not just steady), is always enabled (or requires enabling), is free running
+(works even if interrupts are masked), whether it is safe to use in a
+Non-Maskable Interrupts (NMI), what the epoch is, and more.
+
.. warning::
- This module is under construction, not ready for use, and the documentation
- is incomplete.
+ This module is still under construction, the API is not yet stable. Also the
+ documentation is incomplete.
SystemClock facade
------------------
diff --git a/pw_sync/docs.rst b/pw_sync/docs.rst
index f2959c8..b5835fb 100644
--- a/pw_sync/docs.rst
+++ b/pw_sync/docs.rst
@@ -969,7 +969,7 @@
This simpler but highly portable class of signaling primitives is intended to
ensure that a portability efficiency tradeoff does not have to be made up front.
-For example we intend to provide a ``pw::sync::Notification`` facade which
+For example we intend to provide a ``pw::sync::ThreadNotification`` facade which
permits a singler consumer to block until an event occurs. This should be
backed by the most efficient native primitive for a target, regardless of
whether that is a semaphore, event flag group, condition variable, or something
@@ -1043,12 +1043,23 @@
* - CMSIS-RTOS API v2 & RTX5
- Planned
+Conditional Variables
+=====================
+We've decided for now to skip on conditional variables. These are constructs,
+which are typically not natively available on RTOSes. CVs would have to be
+backed by a multiple hidden semaphore(s) in addition to the explicit public
+mutex. In other words a CV typically ends up as a a composition of
+synchronization primitives on RTOSes. That being said, one could implement them
+using our semaphore and mutex layers and we may consider providing this in the
+future. However for most of our resource constrained customers they will mostly
+likely be using semaphores more often than CVs.
+
Coming Soon
===========
We are intending to provide facades for:
-* ``pw::sync::Notification``: A portable abstraction to allow threads to receive
- notification of a single occurrence of a single event.
+* ``pw::sync::ThreadNotification``: A portable abstraction to allow a single
+ thread to receive notification of a single occurrence of a single event.
* ``pw::sync::EventGroup`` A facade for a common primitive on RTOSes like
FreeRTOS, RTX5, ThreadX, and embOS which permit threads and interrupts to
diff --git a/pw_thread/docs.rst b/pw_thread/docs.rst
index c2b5ef3..99003cd 100644
--- a/pw_thread/docs.rst
+++ b/pw_thread/docs.rst
@@ -27,6 +27,13 @@
``pw::thread::ThreadCore`` objects and functions which match the
``pw::thread::Thread::ThreadRoutine`` signature.
+We recognize that the C++11's STL ``std::thread``` API has some drawbacks where
+it is easy to forget to join or detach the thread handle. Because of this, we
+offer helper wrappers like the ``pw::thread::DetachedThread``. Soon we will
+extend this by also adding a ``pw::thread::JoiningThread`` helper wrapper which
+will also have a lighter weight C++20 ``std::jthread`` like cooperative
+cancellation contract to make joining safer and easier.
+
Threads may begin execution immediately upon construction of the associated
thread object (pending any OS scheduling delays), starting at the top-level
function provided as a constructor argument. The return value of the