xref: /aosp_15_r20/external/pigweed/pw_sync_freertos/docs.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1.. _module-pw_sync_freertos:
2
3================
4pw_sync_freertos
5================
6This is a set of backends for pw_sync based on FreeRTOS.
7
8--------------------------------
9Critical Section Lock Primitives
10--------------------------------
11
12Mutex & TimedMutex
13==================
14The FreeRTOS backend for the Mutex and TimedMutex use ``StaticSemaphore_t`` as
15the underlying type. It is created using ``xSemaphoreCreateMutexStatic`` as part
16of the constructors and cleaned up using ``vSemaphoreDelete`` in the
17destructors.
18
19.. Note::
20  Static allocation support is required in your FreeRTOS configuration, i.e.
21  ``configSUPPORT_STATIC_ALLOCATION == 1``.
22
23InterruptSpinLock
24=================
25The FreeRTOS backend for InterruptSpinLock is backed by ``UBaseType_t`` and a
26``bool`` which permits these objects to stash the saved interrupt mask and to
27detect accidental recursive locking.
28
29This object uses ``taskENTER_CRITICAL_FROM_ISR`` and
30``taskEXIT_CRITICAL_FROM_ISR`` from interrupt contexts, and
31``taskENTER_CRITICAL`` and ``taskEXIT_CRITICAL`` in all other contexts.
32``vTaskSuspendAll`` and ``xTaskResumeAll`` are additionally used within
33lock/unlock respectively when called from task context in the scheduler-enabled
34state.
35
36.. Note::
37  Scheduler State API support is required in your FreeRTOS Configuration, i.e.
38  ``INCLUDE_xTaskGetSchedulerState == 1``.
39
40.. warning::
41  ``taskENTER_CRITICAL_FROM_ISR`` only disables interrupts with priority at or
42  below ``configMAX_SYSCALL_INTERRUPT_PRIORITY``. Therefore, it is unsafe to
43  use InterruptSpinLock from higher-priority interrupts, even if they are not
44  non-maskable interrupts. This is consistent with the rest of the FreeRTOS
45  APIs, see the `FreeRTOS kernel interrupt priority documentation
46  <https://www.freertos.org/a00110.html#kernel_priority>`_ for more details.
47
48Design Notes
49------------
50FreeRTOS does not supply an interrupt spin-lock API, so this backend provides
51a suitable implementation using a compbination of both critical section and
52schduler APIs provided by FreeRTOS.
53
54This design is influenced by the following factors:
55
56- FreeRTOS support for both synchronous and asynchronous yield behavior in
57  different ports.
58- Critical sections behave differently depending on whether or not yield is
59  synchronous or asynchronous.
60- Users must be allowed to call functions that result in a call to yield
61  while an InterruptSpinLock is held.
62- The signaling mechanisms in FreeRTOS all internally call yield to preempt
63  the currently-running task in the event that a higher-priority task is
64  unblocked during execution.
65
66Synchronous and Asynchronous Yield
67^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
68In FreeRTOS, any kernel API call that results in a higher-priority task being
69made “ready” triggers a call to ``taskYIELD()``.
70
71In some ports, this results in an immediate context switch directly from
72within the API - this is known as synchronous yielding behavior.
73
74In other cases, this results in a software-triggered interrupt
75being pended - and depending on the state of interrupts being masked, this
76results in thread-scheduling being deferred until interrupts are unmasked.
77This is known as asynchronous yielding behavior.
78
79As part of a yield, it is left to the port-specific code to call
80the FreeRTOS ``vTaskSwitchContext()`` function to swap current/ready tasks.
81This function will select the next task to run, and swap it for the
82currently executing task.
83
84Yield Within a Critical Section
85^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
86A FreeRTOS critical section provides an interrupt-disabled context that ensures
87that a thread of execution cannot be interrupted by incoming ISRs.
88
89If a port implements asynchronous yield, any calls to ``taskYIELD()`` that
90occur during execution of a critical section will not be handled until the
91interrupts are re-enabled at the end of the critical section.  As a result,
92any higher priority tasks that are unblocked will not preempt the current task
93from within the critical section. In these ports, a critical section alone is
94sufficient to prevent any interruption to code flow - be it from preempting
95tasks or ISRs.
96
97If a port implements synchronous yield, then a context switch to a
98higher-priority ready task can occur within a critical section as a result
99of a kernel API unblocking a higher-prirority task. When this occurs, the
100higher-priority task will be swapped in immediately, and its interrupt-enabled
101status applied to the CPU core. This typically causes interrupts to be
102re-enabled as a result of the context switch, which is an unintended
103side-effect for tasks that presume to have exclusive access to the CPU,
104leading to logic errors and broken assumptions.
105
106In short, any code that uses a FreeRTOS interrupt-disabled critical section
107alone to provide an interrupt-safe context is subject to port-specific behavior
108if it calls kernel APIs that can unblock tasks. A critical section alone is
109insufficient to implement InterruptSpinLock correctly.
110
111Yielding with Scheduling Suspended
112^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
113If a task is unblocked while the scheduler is suspended, the task is moved
114to a "pending ready-list", and a flag is set to ensure that tasks are
115scheduled as necessary once the scheduler is resumed.  Once scheduling
116resumes, any tasks that were unblocked while the scheduler was suspended
117are processed immediately, and rescheduling/preemption resumes at that time.
118
119In the event that a call to ``taskYIELD()`` occurs directly while the
120scheduler is suspended, the result is that ``vTaskSwitchContext()`` switches
121back to the currently running task.  This is a guard-rail that short-circuits
122any attempts to bypass the scheduler-suspended state manually.
123
124Critical Section with Suspended Scheduling
125^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
126It is important to note that a critical section may be entered while the
127scheduler is also disabled. In such a state, the system observes FreeRTOS'
128contract that threads are not re-scheduled while the scheduler is supsended,
129with the benefit that ISRs may not break the atomicity of code executing
130while the lock is held.
131
132This state is also compatible with either synchronous or asynchronous
133yield behavior:
134
135- In the synchronous cases, the result of a call to yield is that
136  ``vTaskSwitchContext`` is invoked immediately, with the current task being
137  restored.
138- In the Asynchronous case, the result of a call to yield is that the context
139  switch interrupt is deferred until the end of the critical section.
140
141This is sufficient to satisfy the requirements implement an InterruptSpinLock
142for any FreeRTOS target.
143
144--------------------
145Signaling Primitives
146--------------------
147
148ThreadNotification & TimedThreadNotification
149============================================
150An optimized FreeRTOS backend for the ThreadNotification and
151TimedThreadNotification is provided using Task Notifications. It is backed by a
152``TaskHandle_t`` and a ``bool`` which permits these objects to track the
153notification value outside of the task's TCB (AKA FreeRTOS Task Notification
154State and Value).
155
156.. Warning::
157  By default this backend uses the task notification at index 0, just like
158  FreeRTOS Stream and Message Buffers. If you want to maintain the state of a
159  task notification across blocking acquiring calls to ThreadNotifications, then
160  you must do one of the following:
161
162  1. Adjust ``PW_SYNC_FREERTOS_CONFIG_THREAD_NOTIFICATION_INDEX`` to an index
163     which does not collide with existing incompatible use.
164  2. Migrate your existing use of task notifications away from index 0.
165  3. Do not use this optimized backend and instead use the binary semaphore
166     backends for ThreadNotifications
167     (``pw_sync:binary_semaphore_thread_notification_backend``).
168
169  You are using any of the following Task Notification APIs, it means you are
170  using notification indices:
171
172  - ``xTaskNotify`` / ``xTaskNotifyIndexed``
173  - ``xTaskNotifyFromISR`` / ``xTaskNotifyIndexedFromISR``
174  - ``xTaskNotifyGive`` / ``xTaskNotifyGiveIndexed``
175  - ``xTaskNotifyGiveFromISR`` / ``xTaskNotifyGiveIndexedFromISR``
176  - ``xTaskNotifyAndQuery`` / ``xTaskNotifyAndQueryIndexed``
177  - ``xTaskNotifyAndQueryFromISR`` / ``xTaskNotifyAndQueryIndexedFromISR``
178  - ``ulTaskNotifyTake`` / ``ulTaskNotifyTakeIndexed``
179  - ``xTaskNotifyWait`` / ``xTaskNotifyWaitIndexed``
180  - ``xTaskNotifyStateClear`` / ``xTaskNotifyStateClearIndexed``
181  - ``ulTaskNotifyValueClear`` / ``ulTaskNotifyValueClearIndexed``
182
183  APIs without ``Indexed`` in the name use index 0 implicitly.
184
185  Prior to FreeRTOS V10.4.0 each task had a single "notification index", and all
186  task notification API functions operated on that implicit index of 0.
187
188This backend is compatible with sharing the notification index
189with native FreeRTOS
190`Stream and Message Buffers <https://www.freertos.org/RTOS-task-notifications.html>`_
191at index 0.
192
193Just like FreeRTOS Stream and Message Buffers, this backend uses the task
194notification index only within callsites where the task must block until a
195notification is received or a timeout occurs. The notification index's state is
196always cleaned up before returning. The notification index is never used when
197the acquiring task is not going to block.
198
199.. Note::
200  Task notification support is required in your FreeRTOS configuration, i.e.
201  ``configUSE_TASK_NOTIFICATIONS == 1``.
202
203Design Notes
204------------
205You may ask, why are Task Notifications used at all given the risk associated
206with global notification index allocations? It turns out there's no other
207lightweight mechanism to unblock a task in FreeRTOS.
208
209Task suspension (i.e. ``vTaskSuspend``, ``vTaskResume``, &
210``vTaskResumeFromISR``) seems like a good fit, however ``xTaskResumeAll`` does
211not participate in reference counting and will wake up all suspended tasks
212whether you want it to or not.
213
214Lastly, there's also ``xTaskAbortDelay`` but there is no interrupt safe
215equivalent of this API. Note that it uses ``vTaskSuspendAll`` internally for
216the critical section which is not interrupt safe. If in the future an interrupt
217safe version of this API is offerred, then this would be a great alternative!
218
219Lastly, we want to briefly explain how Task Notifications actually work in
220FreeRTOS to show why you cannot directly share notification indeces even if the
221bits used in the ``ulNotifiedValue`` are unique. This is a very common source of
222bugs when using FreeRTOS and partially why Pigweed does not recommend using the
223native Task Notification APIs directly.
224
225FreeRTOS Task Notifications use a task's TCB's ``ucNotifyState`` to capture the
226notification state even when the task is not blocked. This state transitions
227``taskNOT_WAITING_NOTIFICATION`` to ``task_NOTIFICATION_RECEIVED`` if the task
228ever notified. This notification state is used to determine whether the next
229task notification wait call should block, irrespective of the notification
230value.
231
232In order to enable this optimized backend, native task notifications are only
233used when the task needs to block. If a timeout occurs the task unregisters for
234notifications and clears the notification state before returning. This exact
235mechanism is used by FreeRTOS internally for their Stream and Message Buffer
236implementations.
237
238One other thing to note is that FreeRTOS has undocumented side effects between
239``vTaskSuspend`` and ``xTaskNotifyWait``. If a thread is suspended via
240``vTaskSuspend`` while blocked on ``xTaskNotifyWait``, the wait is aborted
241regardless of the timeout (even if the request was indefinite) and the thread
242is resumed whenever ``vTaskResume`` is invoked.
243
244BinarySemaphore
245===============
246The FreeRTOS backend for the BinarySemaphore uses ``StaticSemaphore_t`` as the
247underlying type. It is created using ``xSemaphoreCreateBinaryStatic`` as part
248of the constructor and cleaned up using ``vSemaphoreDelete`` in the destructor.
249
250.. Note::
251  Static allocation support is required in your FreeRTOS configuration, i.e.
252  ``configSUPPORT_STATIC_ALLOCATION == 1``.
253
254CountingSemaphore
255=================
256The FreeRTOS backend for the CountingSemaphore uses ``StaticSemaphore_t`` as the
257underlying type. It is created using ``xSemaphoreCreateCountingStatic`` as part
258of the constructor and cleaned up using ``vSemaphoreDelete`` in the destructor.
259
260.. Note::
261  Counting semaphore support is required in your FreeRTOS configuration, i.e.
262  ``configUSE_COUNTING_SEMAPHORES == 1``.
263.. Note::
264  Static allocation support is required in your FreeRTOS configuration, i.e.
265  ``configSUPPORT_STATIC_ALLOCATION == 1``.
266