aboutsummaryrefslogtreecommitdiff
path: root/docs/os/index.rst
blob: 4ed4a6acbf0f5917911b3b811547a995003f6a1b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
.. _docs-os:

==========
OS Support
==========

.. toctree::
   :hidden:

   zephyr/index

Pigweed’s operating system abstraction layers are portable and configurable
building blocks, giving users full control while maintaining high performance
and low overhead.

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!

Pigweed has ports for the following systems:

.. list-table::

  * - **Environment**
    - **Status**
  * - STL (Mac, Window, & Linux)
    - **✔ Supported**
  * - `FreeRTOS <https://www.freertos.org/>`_
    - **✔ Supported**
  * - `Azure RTOS (formerly ThreadX) <https://azure.microsoft.com/en-us/services/rtos/>`_
    - **✔ Supported**
  * - `SEGGER embOS <https://www.segger.com/products/rtos/embos/>`_
    - **✔ Supported**
  * - Baremetal
    - *In Progress*
  * - :ref:`Zephyr <docs-os-zephyr>`
    - *In Progress*
  * - `CMSIS-RTOS API v2 & RTX5 <https://www.keil.com/pack/doc/CMSIS/RTOS2/html/index.html>`_
    - Planned

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; for example, ``std::lock_guard``.

---------------
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


System Clock
============
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;
  }

--------------------------
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 single 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, direct task notification with a critical section, or
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();
  }

--------------------
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_thread_freertos`
    - :ref:`module-pw_thread_freertos`
  * - ThreadX
    - :ref:`module-pw_thread_threadx`
    - :ref:`module-pw_thread_threadx`
  * - embOS
    - :ref:`module-pw_thread_embos`
    - :ref:`module-pw_thread_embos`
  * - STL
    - :ref:`module-pw_thread_stl`
    - :ref:`module-pw_thread_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
------------------
Code runs in *execution contexts*. Common examples of execution contexts on
microcontrollers are **thread context** and **interrupt context**, though there
are others. Since OS abstractions deal with concurrency, it's important to
understand what API primitives are safe to call in what contexts.  Since the
number of execution contexts is too large for Pigweed to cover exhaustively,
Pigweed has the following classes of APIs:

**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**, instead
only atomic signaling can be used. An interrupt safe API may always be
used in a context which permits interrupt safe and thread safe APIs.

On naming
=========
Instead of having context specific APIs like FreeRTOS's ``...FromISR()``,
Pigweed has a single API which validates the context requirements through
``DASSERT`` and ``DCHECK`` in the backends (user configurable). We did this for
a few reasons:

#. **Too many contexts** - Since there are contexts beyond just thread,
   interrupt, and NMI, having context-specific APIs would be a hard to
   maintain. The proliferation of postfixed APIs (``...FromISR``,
   ``...FromNMI``, ``...FromThreadCriticalSection``, and so on) would also be
   confusing for users.

#. **Must verify context anyway** - Backends are required to enforce context
   requirements with ``DCHECK`` or related calls, so we chose a simple API
   which happens to match both the C++'s STL and Google's Abseil.

#. **Multi-context code** - Code running in multiple contexts would need to be
   duplicated for each context if the APIs were postfixed, or duplicated with
   macros. The authors chose the duplication/macro route in previous projects
   and found it clunky and hard to maintain.

-----------------------------
Construction & Initialization
-----------------------------
**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_cortex_m`, 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. We'd like to offer a system clock based timer abstraction facade which can be
   used on either an RTOS or a hardware timer.
2. 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.
3. Cooperative cancellation thread joining along with a ``std::jthread`` like
   wrapper is in progress.
4. We'd like to add support for queues, message queues, and similar channel
   abstractions which also support interprocessor communication in a transparent
   manner.
5. We're interested in supporting asynchronous worker queues and worker queue
   pools.
6. 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.
7. Baremetal support is partially in place today, but it's not ready for use.
8. Most of our APIs today are focused around synchronous blocking APIs, however
   we would love to extend this to include asynchronous APIs.