aboutsummaryrefslogtreecommitdiff
path: root/pw_thread_freertos/docs.rst
blob: d0f22c2c20a43c809515f5c7e399f2b9599e493a (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
.. _module-pw_thread_freertos:

==================
pw_thread_freertos
==================
This is a set of backends for pw_thread based on FreeRTOS.

.. Warning::
  This module is still under construction, the API is not yet stable.

-----------------------
Thread Creation Backend
-----------------------
A backend for ``pw::thread::Thread`` is offered using ``xTaskCreateStatic()``.
Optional dynamic allocation for threads is supported using ``xTaskCreate()``.
Optional joining support is enabled via an ``StaticEventGroup_t`` in each
thread's context.

This backend always permits users to start threads where static contexts are
passed in as an option. As a quick example, a detached thread can be created as
follows:

.. code-block:: cpp

  #include "FreeRTOS.h"
  #include "pw_thread/detached_thread.h"
  #include "pw_thread_freertos/config.h"
  #include "pw_thread_freertos/context.h"
  #include "pw_thread_freertos/options.h"

  constexpr UBaseType_t kFooPriority =
      pw::thread::freertos::config::kDefaultPriority;
  constexpr size_t kFooStackSizeWords =
      pw::thread::freertos::config::kDefaultStackSizeWords;

  pw::thread::freertos::StaticContextWithStack<kFooStackSizeWords>
      example_thread_context;
  void StartExampleThread() {
    pw::thread::DetachedThread(
        pw::thread::freertos::Options()
            .set_name("static_example_thread")
            .set_priority(kFooPriority)
            .set_static_context(example_thread_context),
        example_thread_function)
  }

Alternatively when ``PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED`` is
enabled, dynamic thread allocation can be used. The above example could instead
be done as follows:

.. code-block:: cpp

  #include "FreeRTOS.h"
  #include "pw_thread/detached_thread.h"
  #include "pw_thread_freertos/config.h"
  #include "pw_thread_freertos/context.h"
  #include "pw_thread_freertos/options.h"

  constexpr UBaseType_t kFooPriority =
      pw::thread::freertos::config::kDefaultPriority;
  constexpr size_t kFooStackSizeWords =
      pw::thread::freertos::config::kDefaultStackSizeWords;

  void StartExampleThread() {
    pw::thread::DetachedThread(
        pw::thread::freertos::Options()
            .set_name("dyanmic_example_thread")
            .set_priority(kFooPriority)
            .set_stack_size(kFooStackSizeWords),
        example_thread_function)
  }


Module Configuration Options
============================
The following configurations can be adjusted via compile-time configuration of
this module, see the
:ref:`module documentation <module-structure-compile-time-configuration>` for
more details.

.. c:macro:: PW_THREAD_FREERTOS_CONFIG_JOINING_ENABLED

  Whether thread joining is enabled. By default this is disabled.

  We suggest only enabling this when thread joining is required to minimize
  the RAM and ROM cost of threads.

  Enabling this grows the RAM footprint of every ``pw::thread::Thread`` as it
  adds a ``StaticEventGroup_t`` to every thread's
  ``pw::thread::freertos::Context``. In addition, there is a minute ROM cost to
  construct and destroy this added object.

  ``PW_THREAD_JOINING_ENABLED`` gets set to this value.

.. c:macro:: PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED

  Whether dynamic allocation for threads (stacks and contexts) is enabled. By
  default this matches the FreeRTOS configuration on whether dynamic
  allocations are enabled. Note that static contexts **must** be provided if
  dynamic allocations are disabled.

.. c:macro:: PW_THREAD_FREERTOS_CONFIG_DEFAULT_STACK_SIZE_WORDS

   The default stack size in words. By default this uses the minimal FreeRTOS
   stack size based on ``configMINIMAL_STACK_SIZE``.

.. c:macro:: PW_THREAD_FREERTOS_CONFIG_DEFAULT_PRIORITY

   The default thread priority. By default this uses the minimal FreeRTOS
   priority level above the idle priority (``tskIDLE_PRIORITY + 1``).

.. c:macro:: PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY

  The maximum thread priority. By default this uses the value below the
  number of priorities defined by the FreeRTOS configuration
  (``configMAX_PRIORITIES - 1``).

.. c:macro:: PW_THREAD_FREERTOS_CONFIG_LOG_LEVEL

  The log level to use for this module. Logs below this level are omitted.

FreeRTOS Thread Options
=======================
.. cpp:class:: pw::thread::freertos::Options

  .. cpp:function:: set_name(const char* name)

    Sets the name for the FreeRTOS task, note that this will be truncated
    based on ``configMAX_TASK_NAME_LEN``. This is deep copied by FreeRTOS into
    the task's task control block (TCB).

  .. cpp:function:: set_priority(UBaseType_t priority)

    Sets the priority for the FreeRTOS task. This must be a value between
    ``0`` to ``PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY``. Higher priority
    values have a higher priority.

    Note that the idle task priority, ``tskIDLE_PRIORITY``, is fixed to ``0``.
    See the `FreeRTOS documentation on the idle task
    <https://www.freertos.org/RTOS-idle-task.html>`_ for more details.

    Precondition: This must be <= PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY.

  .. cpp:function:: set_stack_size(size_t size_words)

    Set the stack size in words for a dynamically thread.

    This is only available if
    ``PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED`` is enabled.

    Precondition: size_words must be >= ``configMINIMAL_STACK_SIZE``

  .. cpp:function:: set_static_context(pw::thread::freertos::Context& context)

    Set the pre-allocated context (all memory needed to run a thread). The
    ``StaticContext`` can either be constructed with an externally provided
    ``std::span<StackType_t>`` stack or the templated form of
    ``StaticContextWithStack<kStackSizeWords>`` can be used.


-----------------------------
Thread Identification Backend
-----------------------------
A backend for ``pw::thread::Id`` and ``pw::thread::get_id()`` is offerred using
``xTaskGetCurrentTaskHandle()``. It uses ``DASSERT`` to ensure that it is not
invoked from interrupt context and if possible that the scheduler has started
via ``xTaskGetSchedulerState()``.

--------------------
Thread Sleep Backend
--------------------
A backend for ``pw::thread::sleep_for()`` and ``pw::thread::sleep_until()`` is
offerred using ``vTaskDelay()`` if the duration is at least one tick, else
``taskYIELD()`` is used. It uses ``pw::this_thread::get_id() != thread::Id()``
to ensure it invoked only from a thread.

--------------------
Thread Yield Backend
--------------------
A backend for ``pw::thread::yield()`` is offered using via ``taskYIELD()``.
It uses ``pw::this_thread::get_id() != thread::Id()`` to ensure it invoked only
from a thread.

---------
utilities
---------
In cases where an operation must be performed for every thread,
``ForEachThread()`` can be used to iterate over all the created thread TCBs.
Note that it's only safe to use this while the scheduler and interrupts are
disabled.

Calling this before the scheduler has started, via ``vTaskStartScheduler()``, is
non-fatal but will result in no action and a ``FailedPrecondition`` error code.

An ``Aborted`` error status is returned if the provided callback returns
``false`` to request an early termination of thread iteration.

Return values
=============

* ``FailedPrecondition``: Returned when ``ForEachThread()`` is run before the OS
  has been initialized.
* ``Aborted``: The callback requested an early-termination of thread iteration.
* ``OkStatus``: The callback has been successfully run with every thread.

.. Note:: This uses an unsupported method to iterate the threads in a more
   efficient manner while also supporting interrupt contexts. This requires
   linking against internal statics from the FreeRTOS kernel,
   :ref:`pw_third_party_freertos_DISABLE_TASKS_STATICS <third_party-freertos_disable_task_statics>`
   must be used.

--------------------
Snapshot integration
--------------------
This ``pw_thread`` backend provides helper functions that capture FreeRTOS
thread state to a ``pw::thread::Thread`` proto.

FreeRTOS tskTCB facade
======================
Unfortunately FreeRTOS entirely hides the contents of the TCB inside of
``Source/tasks.c``, but it's necessary for snapshot processing in order to
access the stack limits from interrupt contexts. For this reason, FreeRTOS
snapshot integration relies on the ``pw_thread_freertos:freertos_tsktcb`` facade
to provide the ``tskTCB`` definition.

The selected backend is expected to provide the ``struct tskTCB`` definition
through ``pw_thread_freertos_backend/freertos_tsktcb.h``. The facade asserts
that this definition matches the size of FreeRTOS's ``StaticTask_T`` which is
the public opaque TCB type.

SnapshotThread()/SnapshotThreads()
==================================
``SnapshotThread()`` captures the thread name, state, and stack information for
the provided TCB to a ``pw::thread::Thread`` protobuf encoder. To ensure
the most up-to-date information is captured, the stack pointer for the currently
running thread must be provided for cases where the running thread is being
captured. For ARM Cortex-M CPUs, you can do something like this:

.. Code:: cpp

  // Capture PSP.
  void* stack_ptr = 0;
  asm volatile("mrs %0, psp\n" : "=r"(stack_ptr));
  pw::thread::ProcessThreadStackCallback cb =
      [](pw::thread::Thread::StreamEncoder& encoder,
         pw::ConstByteSpan stack) -> pw::Status {
    return encoder.WriteRawStack(stack);
  };
  pw::thread::threadx::SnapshotThread(my_thread, thread_state, stack_ptr,
                                      snapshot_encoder, cb);

``SnapshotThreads()`` wraps the singular thread capture to instead captures
all created threads to a ``pw::thread::SnapshotThreadInfo`` message which also
captures the thread state for you. This proto
message overlays a snapshot, so it is safe to static cast a
``pw::snapshot::Snapshot::StreamEncoder`` to a
``pw::thread::SnapshotThreadInfo::StreamEncoder`` when calling this function.

.. Note:: ``SnapshotThreads()`` is only safe to use this while the scheduler and
   interrupts are disabled as it relies on ``ForEachThread()``.

Thread Stack Capture
--------------------
Snapshot attempts to capture as much of the thread stack state as possible,
however it can be limited by on the FreeRTOS configuration.

The ``stack_start_ptr`` can only be provided if the ``portSTACK_GROWTH`` is < 0,
i.e. the stack grows down, when ``configRECORD_STACK_HIGH_ADDRESS`` is enabled.

The ``stack_pointer_est_peak`` can only be provided when
``config_USE_TRACE_FACILITY`` and/or ``INCLUDE_uxTaskGetStackHighWaterMark`` are
enabled and ``stack_start_ptr``'s requirements above are met.