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