268 lines
12 KiB
ReStructuredText
268 lines
12 KiB
ReStructuredText
.. _module-pw_sync_freertos:
|
|
|
|
================
|
|
pw_sync_freertos
|
|
================
|
|
This is a set of backends for pw_sync based on FreeRTOS.
|
|
|
|
--------------------------------
|
|
Critical Section Lock Primitives
|
|
--------------------------------
|
|
|
|
Mutex & TimedMutex
|
|
==================
|
|
The FreeRTOS backend for the Mutex and TimedMutex use ``StaticSemaphore_t`` as
|
|
the underlying type. It is created using ``xSemaphoreCreateMutexStatic`` as part
|
|
of the constructors and cleaned up using ``vSemaphoreDelete`` in the
|
|
destructors.
|
|
|
|
.. Note::
|
|
Static allocation support is required in your FreeRTOS configuration, i.e.
|
|
``configSUPPORT_STATIC_ALLOCATION == 1``.
|
|
|
|
InterruptSpinLock
|
|
=================
|
|
The FreeRTOS backend for InterruptSpinLock is backed by ``UBaseType_t`` and a
|
|
``bool`` which permits these objects to stash the saved interrupt mask and to
|
|
detect accidental recursive locking.
|
|
|
|
This object uses ``taskENTER_CRITICAL_FROM_ISR`` and
|
|
``taskEXIT_CRITICAL_FROM_ISR`` from interrupt contexts, and
|
|
``taskENTER_CRITICAL`` and ``taskEXIT_CRITICAL`` in all other contexts.
|
|
``vTaskSuspendAll`` and ``xTaskResumeAll`` are additionally used within
|
|
lock/unlock respectively when called from task context in the scheduler-enabled
|
|
state.
|
|
|
|
.. Note::
|
|
Scheduler State API support is required in your FreeRTOS Configuration, i.e.
|
|
``INCLUDE_xTaskGetSchedulerState == 1``.
|
|
|
|
.. warning::
|
|
``taskENTER_CRITICAL_FROM_ISR`` only disables interrupts with priority at or
|
|
below ``configMAX_SYSCALL_INTERRUPT_PRIORITY``. Therefore, it is unsafe to
|
|
use InterruptSpinLock from higher-priority interrupts, even if they are not
|
|
non-maskable interrupts. This is consistent with the rest of the FreeRTOS
|
|
APIs, see the `FreeRTOS kernel interrupt priority documentation
|
|
<https://www.freertos.org/a00110.html#kernel_priority>`_ for more details.
|
|
|
|
Design Notes
|
|
------------
|
|
FreeRTOS does not supply an interrupt spin-lock API, so this backend provides
|
|
a suitable implementation using a compbination of both critical section and
|
|
schduler APIs provided by FreeRTOS.
|
|
|
|
This design is influenced by the following factors:
|
|
|
|
- FreeRTOS support for both synchronous and asynchronous yield behavior in
|
|
different ports.
|
|
- Critical sections behave differently depending on whether or not yield is
|
|
synchronous or asynchronous.
|
|
- Users must be allowed to call functions that result in a call to yield
|
|
while an InterruptSpinLock is held.
|
|
- The signaling mechanisms in FreeRTOS all internally call yield to preempt
|
|
the currently-running task in the event that a higher-priority task is
|
|
unblocked during execution.
|
|
|
|
Synchronous and Asynchronous Yield
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
In FreeRTOS, any kernel API call that results in a higher-priority task being
|
|
made “ready” triggers a call to ``taskYIELD()``.
|
|
|
|
In some ports, this results in an immediate context switch directly from
|
|
within the API - this is known as synchronous yielding behavior.
|
|
|
|
In other cases, this results in a software-triggered interrupt
|
|
being pended - and depending on the state of interrupts being masked, this
|
|
results in thread-scheduling being deferred until interrupts are unmasked.
|
|
This is known as asynchronous yielding behavior.
|
|
|
|
As part of a yield, it is left to the port-specific code to call
|
|
the FreeRTOS ``vTaskSwitchContext()`` function to swap current/ready tasks.
|
|
This function will select the next task to run, and swap it for the
|
|
currently executing task.
|
|
|
|
Yield Within a Critical Section
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
A FreeRTOS critical section provides an interrupt-disabled context that ensures
|
|
that a thread of execution cannot be interrupted by incoming ISRs.
|
|
|
|
If a port implements asynchronous yield, any calls to ``taskYIELD()`` that
|
|
occur during execution of a critical section will not be handled until the
|
|
interrupts are re-enabled at the end of the critical section. As a result,
|
|
any higher priority tasks that are unblocked will not preempt the current task
|
|
from within the critical section. In these ports, a critical section alone is
|
|
sufficient to prevent any interruption to code flow - be it from preempting
|
|
tasks or ISRs.
|
|
|
|
If a port implements synchronous yield, then a context switch to a
|
|
higher-priority ready task can occur within a critical section as a result
|
|
of a kernel API unblocking a higher-prirority task. When this occurs, the
|
|
higher-priority task will be swapped in immediately, and its interrupt-enabled
|
|
status applied to the CPU core. This typically causes interrupts to be
|
|
re-enabled as a result of the context switch, which is an unintended
|
|
side-effect for tasks that presume to have exclusive access to the CPU,
|
|
leading to logic errors and broken assumptions.
|
|
|
|
In short, any code that uses a FreeRTOS interrupt-disabled critical section
|
|
alone to provide an interrupt-safe context is subject to port-specific behavior
|
|
if it calls kernel APIs that can unblock tasks. A critical section alone is
|
|
insufficient to implement InterruptSpinLock correctly.
|
|
|
|
Yielding with Scheduling Suspended
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
If a task is unblocked while the scheduler is suspended, the task is moved
|
|
to a "pending ready-list", and a flag is set to ensure that tasks are
|
|
scheduled as necessary once the scheduler is resumed. Once scheduling
|
|
resumes, any tasks that were unblocked while the scheduler was suspended
|
|
are processed immediately, and rescheduling/preemption resumes at that time.
|
|
|
|
In the event that a call to ``taskYIELD()`` occurs directly while the
|
|
scheduler is suspended, the result is that ``vTaskSwitchContext()`` switches
|
|
back to the currently running task. This is a guard-rail that short-circuits
|
|
any attempts to bypass the scheduler-suspended state manually.
|
|
|
|
Critical Section with Suspended Scheduling
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
It is important to note that a critical section may be entered while the
|
|
scheduler is also disabled. In such a state, the system observes FreeRTOS'
|
|
contract that threads are not re-scheduled while the scheduler is supsended,
|
|
with the benefit that ISRs may not break the atomicity of code executing
|
|
while the lock is held.
|
|
|
|
This state is also compatible with either synchronous or asynchronous
|
|
yield behavior:
|
|
|
|
- In the synchronous cases, the result of a call to yield is that
|
|
``vTaskSwitchContext`` is invoked immediately, with the current task being
|
|
restored.
|
|
- In the Asynchronous case, the result of a call to yield is that the context
|
|
switch interrupt is deferred until the end of the critical section.
|
|
|
|
This is sufficient to satisfy the requirements implement an InterruptSpinLock
|
|
for any FreeRTOS target.
|
|
|
|
--------------------
|
|
Signaling Primitives
|
|
--------------------
|
|
|
|
ThreadNotification & TimedThreadNotification
|
|
============================================
|
|
An optimized FreeRTOS backend for the ThreadNotification and
|
|
TimedThreadNotification is provided using Task Notifications. It is backed by a
|
|
``TaskHandle_t`` and a ``bool`` which permits these objects to track the
|
|
notification value outside of the task's TCB (AKA FreeRTOS Task Notification
|
|
State and Value).
|
|
|
|
.. Warning::
|
|
By default this backend uses the task notification at index 0, just like
|
|
FreeRTOS Stream and Message Buffers. If you want to maintain the state of a
|
|
task notification across blocking acquiring calls to ThreadNotifications, then
|
|
you must do one of the following:
|
|
|
|
1. Adjust ``PW_SYNC_FREERTOS_CONFIG_THREAD_NOTIFICATION_INDEX`` to an index
|
|
which does not collide with existing incompatible use.
|
|
2. Migrate your existing use of task notifications away from index 0.
|
|
3. Do not use this optimized backend and instead use the binary semaphore
|
|
backends for ThreadNotifications
|
|
(``pw_sync:binary_semaphore_thread_notification_backend``).
|
|
|
|
You are using any of the following Task Notification APIs, it means you are
|
|
using notification indices:
|
|
|
|
- ``xTaskNotify`` / ``xTaskNotifyIndexed``
|
|
- ``xTaskNotifyFromISR`` / ``xTaskNotifyIndexedFromISR``
|
|
- ``xTaskNotifyGive`` / ``xTaskNotifyGiveIndexed``
|
|
- ``xTaskNotifyGiveFromISR`` / ``xTaskNotifyGiveIndexedFromISR``
|
|
- ``xTaskNotifyAndQuery`` / ``xTaskNotifyAndQueryIndexed``
|
|
- ``xTaskNotifyAndQueryFromISR`` / ``xTaskNotifyAndQueryIndexedFromISR``
|
|
- ``ulTaskNotifyTake`` / ``ulTaskNotifyTakeIndexed``
|
|
- ``xTaskNotifyWait`` / ``xTaskNotifyWaitIndexed``
|
|
- ``xTaskNotifyStateClear`` / ``xTaskNotifyStateClearIndexed``
|
|
- ``ulTaskNotifyValueClear`` / ``ulTaskNotifyValueClearIndexed``
|
|
|
|
APIs without ``Indexed`` in the name use index 0 implicitly.
|
|
|
|
Prior to FreeRTOS V10.4.0 each task had a single "notification index", and all
|
|
task notification API functions operated on that implicit index of 0.
|
|
|
|
This backend is compatible with sharing the notification index
|
|
with native FreeRTOS
|
|
`Stream and Message Buffers <https://www.freertos.org/RTOS-task-notifications.html>`_
|
|
at index 0.
|
|
|
|
Just like FreeRTOS Stream and Message Buffers, this backend uses the task
|
|
notification index only within callsites where the task must block until a
|
|
notification is received or a timeout occurs. The notification index's state is
|
|
always cleaned up before returning. The notification index is never used when
|
|
the acquiring task is not going to block.
|
|
|
|
.. Note::
|
|
Task notification support is required in your FreeRTOS configuration, i.e.
|
|
``configUSE_TASK_NOTIFICATIONS == 1``.
|
|
|
|
Design Notes
|
|
------------
|
|
You may ask, why are Task Notifications used at all given the risk associated
|
|
with global notification index allocations? It turns out there's no other
|
|
lightweight mechanism to unblock a task in FreeRTOS.
|
|
|
|
Task suspension (i.e. ``vTaskSuspend``, ``vTaskResume``, &
|
|
``vTaskResumeFromISR``) seems like a good fit, however ``xTaskResumeAll`` does
|
|
not participate in reference counting and will wake up all suspended tasks
|
|
whether you want it to or not.
|
|
|
|
Lastly, there's also ``xTaskAbortDelay`` but there is no interrupt safe
|
|
equivalent of this API. Note that it uses ``vTaskSuspendAll`` internally for
|
|
the critical section which is not interrupt safe. If in the future an interrupt
|
|
safe version of this API is offerred, then this would be a great alternative!
|
|
|
|
Lastly, we want to briefly explain how Task Notifications actually work in
|
|
FreeRTOS to show why you cannot directly share notification indeces even if the
|
|
bits used in the ``ulNotifiedValue`` are unique. This is a very common source of
|
|
bugs when using FreeRTOS and partially why Pigweed does not recommend using the
|
|
native Task Notification APIs directly.
|
|
|
|
FreeRTOS Task Notifications use a task's TCB's ``ucNotifyState`` to capture the
|
|
notification state even when the task is not blocked. This state transitions
|
|
``taskNOT_WAITING_NOTIFICATION`` to ``task_NOTIFICATION_RECEIVED`` if the task
|
|
ever notified. This notification state is used to determine whether the next
|
|
task notification wait call should block, irrespective of the notification
|
|
value.
|
|
|
|
In order to enable this optimized backend, native task notifications are only
|
|
used when the task needs to block. If a timeout occurs the task unregisters for
|
|
notifications and clears the notification state before returning. This exact
|
|
mechanism is used by FreeRTOS internally for their Stream and Message Buffer
|
|
implementations.
|
|
|
|
One other thing to note is that FreeRTOS has undocumented side effects between
|
|
``vTaskSuspend`` and ``xTaskNotifyWait``. If a thread is suspended via
|
|
``vTaskSuspend`` while blocked on ``xTaskNotifyWait``, the wait is aborted
|
|
regardless of the timeout (even if the request was indefinite) and the thread
|
|
is resumed whenever ``vTaskResume`` is invoked.
|
|
|
|
BinarySemaphore
|
|
===============
|
|
The FreeRTOS backend for the BinarySemaphore uses ``StaticSemaphore_t`` as the
|
|
underlying type. It is created using ``xSemaphoreCreateBinaryStatic`` as part
|
|
of the constructor and cleaned up using ``vSemaphoreDelete`` in the destructor.
|
|
|
|
.. Note::
|
|
Static allocation support is required in your FreeRTOS configuration, i.e.
|
|
``configSUPPORT_STATIC_ALLOCATION == 1``.
|
|
|
|
CountingSemaphore
|
|
=================
|
|
The FreeRTOS backend for the CountingSemaphore uses ``StaticSemaphore_t`` as the
|
|
underlying type. It is created using ``xSemaphoreCreateCountingStatic`` as part
|
|
of the constructor and cleaned up using ``vSemaphoreDelete`` in the destructor.
|
|
|
|
.. Note::
|
|
Counting semaphore support is required in your FreeRTOS configuration, i.e.
|
|
``configUSE_COUNTING_SEMAPHORES == 1``.
|
|
.. Note::
|
|
Static allocation support is required in your FreeRTOS configuration, i.e.
|
|
``configSUPPORT_STATIC_ALLOCATION == 1``.
|
|
|
|
|