1.. _module-pw_thread_freertos: 2 3================== 4pw_thread_freertos 5================== 6This is a set of backends for pw_thread based on FreeRTOS. 7 8.. Warning:: 9 This module is still under construction, the API is not yet stable. 10 11----------------------- 12Thread Creation Backend 13----------------------- 14A backend for ``pw::Thread`` is offered using ``xTaskCreateStatic()``. 15Optional dynamic allocation for threads is supported using ``xTaskCreate()``. 16Optional joining support is enabled via an ``StaticEventGroup_t`` in each 17thread's context. 18 19.. Note:: 20 Scheduler State API support is required in your FreeRTOS Configuration, i.e. 21 ``INCLUDE_xTaskGetSchedulerState == 1``. 22 23This backend always permits users to start threads where static contexts are 24passed in as an option. As a quick example, a detached thread can be created as 25follows: 26 27.. code-block:: cpp 28 29 #include "FreeRTOS.h" 30 #include "pw_thread/detached_thread.h" 31 #include "pw_thread_freertos/config.h" 32 #include "pw_thread_freertos/context.h" 33 #include "pw_thread_freertos/options.h" 34 35 constexpr UBaseType_t kFooPriority = 36 pw::thread::freertos::config::kDefaultPriority; 37 constexpr size_t kFooStackSizeWords = 38 pw::thread::freertos::config::kDefaultStackSizeWords; 39 40 pw::thread::freertos::StaticContextWithStack<kFooStackSizeWords> 41 example_thread_context; 42 void StartExampleThread() { 43 pw::thread::DetachedThread( 44 pw::thread::freertos::Options() 45 .set_name("static_example_thread") 46 .set_priority(kFooPriority) 47 .set_static_context(example_thread_context), 48 example_thread_function); 49 } 50 51Alternatively when ``PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED`` is 52enabled, dynamic thread allocation can be used. The above example could instead 53be done as follows: 54 55.. code-block:: cpp 56 57 #include "FreeRTOS.h" 58 #include "pw_thread/detached_thread.h" 59 #include "pw_thread_freertos/config.h" 60 #include "pw_thread_freertos/context.h" 61 #include "pw_thread_freertos/options.h" 62 63 constexpr UBaseType_t kFooPriority = 64 pw::thread::freertos::config::kDefaultPriority; 65 constexpr size_t kFooStackSizeWords = 66 pw::thread::freertos::config::kDefaultStackSizeWords; 67 68 void StartExampleThread() { 69 pw::thread::DetachedThread( 70 pw::thread::freertos::Options() 71 .set_name("dyanmic_example_thread") 72 .set_priority(kFooPriority) 73 .set_stack_size(kFooStackSizeWords), 74 example_thread_function) 75 } 76 77 78Module Configuration Options 79============================ 80The following configurations can be adjusted via compile-time configuration of 81this module, see the 82:ref:`module documentation <module-structure-compile-time-configuration>` for 83more details. 84 85.. c:macro:: PW_THREAD_FREERTOS_CONFIG_JOINING_ENABLED 86 87 Whether thread joining is enabled. By default this is disabled. 88 89 We suggest only enabling this when thread joining is required to minimize 90 the RAM and ROM cost of threads. 91 92 Enabling this grows the RAM footprint of every ``pw::Thread`` as it adds a 93 ``StaticEventGroup_t`` to every thread's ``pw::thread::freertos::Context``. In 94 addition, there is a minute ROM cost to construct and destroy this added 95 object. 96 97 ``PW_THREAD_JOINING_ENABLED`` gets set to this value. 98 99.. c:macro:: PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED 100 101 Whether dynamic allocation for threads (stacks and contexts) is enabled. By 102 default this matches the FreeRTOS configuration on whether dynamic 103 allocations are enabled. Note that static contexts **must** be provided if 104 dynamic allocations are disabled. 105 106.. c:macro:: PW_THREAD_FREERTOS_CONFIG_DEFAULT_STACK_SIZE_WORDS 107 108 The default stack size in words. By default this uses the minimal FreeRTOS 109 stack size based on ``configMINIMAL_STACK_SIZE``. 110 111.. c:macro:: PW_THREAD_FREERTOS_CONFIG_DEFAULT_PRIORITY 112 113 The default thread priority. By default this uses the minimal FreeRTOS 114 priority level above the idle priority (``tskIDLE_PRIORITY + 1``). 115 116.. c:macro:: PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY 117 118 The maximum thread priority. By default this uses the value below the 119 number of priorities defined by the FreeRTOS configuration 120 (``configMAX_PRIORITIES - 1``). 121 122.. c:macro:: PW_THREAD_FREERTOS_CONFIG_LOG_LEVEL 123 124 The log level to use for this module. Logs below this level are omitted. 125 126FreeRTOS Thread Options 127======================= 128.. cpp:class:: pw::thread::freertos::Options 129 130 .. cpp:function:: set_name(const char* name) 131 132 Sets the name for the FreeRTOS task, note that this will be truncated 133 based on ``configMAX_TASK_NAME_LEN``. This is deep copied by FreeRTOS into 134 the task's task control block (TCB). 135 136 .. cpp:function:: set_priority(UBaseType_t priority) 137 138 Sets the priority for the FreeRTOS task. This must be a value between 139 ``0`` to ``PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY``. Higher priority 140 values have a higher priority. 141 142 Note that the idle task priority, ``tskIDLE_PRIORITY``, is fixed to ``0``. 143 See the `FreeRTOS documentation on the idle task 144 <https://www.freertos.org/RTOS-idle-task.html>`_ for more details. 145 146 Precondition: This must be <= PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY. 147 148 .. cpp:function:: set_stack_size(size_t size_words) 149 150 Set the stack size in words for a dynamically thread. 151 152 This is only available if 153 ``PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED`` is enabled. 154 155 Precondition: size_words must be >= ``configMINIMAL_STACK_SIZE`` 156 157 .. cpp:function:: set_static_context(pw::thread::freertos::Context& context) 158 159 Set the pre-allocated context (all memory needed to run a thread). The 160 ``StaticContext`` can either be constructed with an externally provided 161 ``pw::span<StackType_t>`` stack or the templated form of 162 ``StaticContextWithStack<kStackSizeWords>`` can be used. 163 164 165----------------------------- 166Thread Identification Backend 167----------------------------- 168A backend for ``pw::Thread::id`` and ``pw::thread::get_id()`` is offered using 169``xTaskGetCurrentTaskHandle()``. It uses ``DASSERT`` to ensure that it is not 170invoked from interrupt context and if possible that the scheduler has started 171via ``xTaskGetSchedulerState()``. 172 173------------------------ 174Thread Iteration Backend 175------------------------ 176``pw_thread_freertos_TSKTCB_BACKEND`` to be configured 177properly and ``pw_third_party_freertos_DISABLE_TASKS_STATICS`` to be enabled. 178To allow for peak stack usage measurement, the FreeRTOS config 179``INCLUDE_uxTaskGetStackHighWaterMark`` should also be enabled. 180 181-------------------- 182Thread Sleep Backend 183-------------------- 184A backend for ``pw::thread::sleep_for()`` and ``pw::thread::sleep_until()`` is 185offerred using ``vTaskDelay()`` if the duration is at least one tick, else 186``taskYIELD()`` is used. It uses ``pw::this_thread::get_id() != Thread::id()`` 187to ensure it invoked only from a thread. 188 189-------------------- 190Thread Yield Backend 191-------------------- 192A backend for ``pw::thread::yield()`` is offered using via ``taskYIELD()``. 193It uses ``pw::this_thread::get_id() != Thread::id()`` to ensure it invoked only 194from a thread. 195 196--------------------------- 197Test Thread Context Backend 198--------------------------- 199A backend for ``pw::thread::TestThreadContext()`` is offered using default 200options and a static stack size of 8192 words. 201 202--------- 203Utilities 204--------- 205``ForEachThread()`` 206=================== 207In cases where an operation must be performed for every thread, 208``ForEachThread()`` can be used to iterate over all the created thread TCBs. 209Note that it's only safe to use this while the scheduler and interrupts are 210disabled. 211 212Calling this before the scheduler has started, via ``vTaskStartScheduler()``, is 213non-fatal but will result in no action and a ``FailedPrecondition`` error code. 214 215An ``Aborted`` error status is returned if the provided callback returns 216``false`` to request an early termination of thread iteration. 217 218*Return values* 219 220* ``FailedPrecondition``: Returned when ``ForEachThread()`` is run before the OS 221 has been initialized. 222* ``Aborted``: The callback requested an early-termination of thread iteration. 223* ``OkStatus``: The callback has been successfully run with every thread. 224 225.. Note:: This uses an unsupported method to iterate the threads in a more 226 efficient manner while also supporting interrupt contexts. This requires 227 linking against internal statics from the FreeRTOS kernel, 228 :ref:`pw_third_party_freertos_DISABLE_TASKS_STATICS <third_party-freertos_disable_task_statics>` 229 must be used. 230 231-------------------- 232Snapshot integration 233-------------------- 234This ``pw_thread`` backend provides helper functions that capture FreeRTOS 235thread state to a ``pw::Thread`` proto. 236 237FreeRTOS tskTCB facade 238====================== 239Unfortunately FreeRTOS entirely hides the contents of the TCB inside of 240``Source/tasks.c``, but it's necessary for snapshot processing in order to 241access the stack limits from interrupt contexts. For this reason, FreeRTOS 242snapshot integration relies on the ``pw_thread_freertos:freertos_tsktcb`` facade 243to provide the ``tskTCB`` definition. By default, a header will automatically be 244generated from FreeRTOS's ``tasks.c`` file to work around this limitation. 245 246In the event that the automatic header generation is incompatible with your 247version of FreeRTOS, ``pw_thread_freertos_FREERTOS_TSKTCB_BACKEND`` must be 248configured to point to a source set that provides the ``struct tskTCB`` 249definition through ``pw_thread_freertos_backend/freertos_tsktcb.h``. The facade 250asserts that this definition matches the size of FreeRTOS's ``StaticTask_T`` 251which is the public opaque TCB type. 252 253``SnapshotThreads()`` 254===================== 255``SnapshotThreads()`` captures the thread name, state, and stack information for 256the provided TCB to a ``pw::Thread`` protobuf encoder. To ensure the most 257up-to-date information is captured, the stack pointer for the currently running 258thread must be provided for cases where the running thread is being captured. 259For ARM Cortex-M CPUs, you can do something like this: 260 261.. code-block:: cpp 262 263 // Capture PSP. 264 void* stack_ptr = 0; 265 asm volatile("mrs %0, psp\n" : "=r"(stack_ptr)); 266 pw::thread::ProcessThreadStackCallback cb = 267 [](pw::thread::proto::Thread::StreamEncoder& encoder, 268 pw::ConstByteSpan stack) -> pw::Status { 269 return encoder.WriteRawStack(stack); 270 }; 271 pw::thread::threadx::SnapshotThread(my_thread, thread_state, stack_ptr, 272 snapshot_encoder, cb); 273 274Some FreeRTOS ports (e.g. RISC-V) automatically store the stack pointer back 275into the running thread's TCB upon exception entry, so there's no need to inject 276an updated stack pointer. If you're unsure of your platform's behavior, inject 277an updated stack pointer captured upon exception entry to be safe. 278 279``SnapshotThreads()`` wraps the singular thread capture to instead captures 280all created threads to a ``pw::thread::proto::SnapshotThreadInfo`` message 281which also captures the thread state for you. This proto 282message overlays a snapshot, so it is safe to static cast a 283``pw::snapshot::Snapshot::StreamEncoder`` to a 284``pw::thread::proto::SnapshotThreadInfo::StreamEncoder`` when calling this 285function. 286 287.. Note:: ``SnapshotThreads()`` is only safe to use this while the scheduler and 288 interrupts are disabled as it relies on ``ForEachThread()``. 289 290Thread Stack Capture 291-------------------- 292Snapshot attempts to capture as much of the thread stack state as possible, 293however it can be limited by the FreeRTOS configuration. 294 295The ``stack_start_ptr`` can only be provided if the ``portSTACK_GROWTH`` is < 0, 296i.e. the stack grows down, when ``configRECORD_STACK_HIGH_ADDRESS`` is enabled. 297 298The ``stack_pointer_est_peak`` can only be provided when 299``config_USE_TRACE_FACILITY`` and/or ``INCLUDE_uxTaskGetStackHighWaterMark`` are 300enabled and ``stack_start_ptr``'s requirements above are met. 301