mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
c2f2b1e228
This commit adds some notes about the SMP FreeRTOS port
8.3 KiB
8.3 KiB
Overview
Terminology
The following terms will be used in this document to avoid confusion between the different FreeRTOS versions currently in ESP-IDF
- SMP FreeRTOS: The SMP branch of the FreeRTOS kernel found here
- IDF FreeRTOS: The version of FreeRTOS used in mainline ESP-IDF that contained custom modifications to support SMP features specific to the ESP chips.
Organization
This directory contains a copy of SMP FreeRTOS based off of upstream commit 483237711
- IDF FreeRTOS remains in
components/freertos/FreeRTOS-Kernel
- SMP FreeRTOS is entirely contained in
components/freertos/FreeRTOS-Kernel-SMP
- The Xtensa port files are copied from IDF FreeRTOS with minimal modifications.
port.c
andportmacro.h
were mostly re-written from scratch- Some changes were made to SMP FreeRTOS
tasks.c
in order to be compatible with IDF.- All of these changes are wrapped in
#ifdef ESP_PLATFORM
- These additions will be removed after the compatibility issues are resolved in IDF.
- All of these changes are wrapped in
- SMP FreeRTOS is only compiled if
CONFIG_FREERTOS_SMP
is set inmenuconfig
- All changes made to the rest of ESP-IDF to support SMP FreeRTOS are wrapped in
#ifdef CONFIG_FREERTOS_SMP
. Thus SMP FreeRTOS changes should have no effect on mainline ESP-IDF.
Behavioral Differences and Porting
This section covers
- The major feature/behavior differences between SMP FreeRTOS vs IDF FreeRTOS
- The changes made to the port or SMP FreeRTOS sources to remedy these differences
- Future Todos
Scheduler Suspension
IDF FreeRTOS:
- Each core had its own suspendable scheduler (i.e., calling
vTaskSuspendAll()
on one core has no effect on the other) - A core that calls
vTaskSuspendAll()
will only disable task switching on that core.
SMP FreeRTOS:
vTaskSuspendAll()
is global (no cores can switch tasks once the scheduler is disabled)- While the scheduler is suspend, any other core that attempts to block, call FreeRTOS API, or also suspend the scheduler will result that the other core spinning with interrupts disabled (due to task lock contention)
Changes Made:
- Replaced all calls of
vTaskSuspendAll()
/xTaskResumeAll()
in ESP-IDF withvTaskPreemptionDisable()
/vTaskPreemptionEnable()
when using SMP FreeRTOS
Critical Sections
IDF FreeRTOS:
- IDF FreeRTOS uses granualar spin locks for critical sections
- Callers need to instantiate and provide their own spinlocks
- But allows different cores to simultaneously enter separate critical sections using different locks without contesting
portENTER_CRITICAL()
/portEXIT_CRITICAL()
modified to accept a spinlock argument- Added variant
portTRY_ENTER_CRITICAL()
to allow for timeout to be specified when entering critical sections
SMP FreeRTOS:
- Uses a giant lock (task and ISR locks to be specific). Cores simultaneously entering a critical section will contest with each other.
portENTER_CRITICAL()
/portEXIT_CRITICAL()
does not accept arguments. Maps directly tovTaskEnterCritical()
/vTaskExitCritical()
- Has added capability of checking for state change, allowing critical section contester to yield
Changes Made:
- In order to be compatible with IDF style critical sections,
portENTER_CRITICAL(...)
/portEXIT_CRITICAL(...)
now acceptsVA_ARGS
- If no argument exists, it calls SMP FreeRTOS critical sections
vTaskEnterCritical()
- If an argument exists, it calls IDF FreeRTOS critical sections (where the argument is the spinlock)
- If no argument exists, it calls SMP FreeRTOS critical sections
Todo:
- Assess how using a giant lock (i.e., SMP FreeRTOS critical sections) will affect performance on time sensitive components of ESP-IDF (e.g., BT, WiFi)
- If performance is acceptable, replace all critical sections in ESP-IDF with SMP FreeRTOS critical sections
- Else, refactor IDF FreeRTOS critical sections to a separate API, and have IDF components call those instead.
Idle Tasks
IDF FreeRTOS:
- Only uses a single idle task function
prvIdleTask()
- An idle task is created for each core and pinned to that core
prvIdleTask()
calls a customesp_vApplicationIdleHook()
, mostly used to feed the task watchdog- IDF adds a Thread Local Storage Pointer (TLSP) Deletion Callback that is called on task deletion (i.e., can be called from the idle task).
SMP FreeRTOS:
- There are now two idle task functions.
prvIdleTask()
andprvMinimalIdleTask()
prvMinimalIdleTask()
simply calls thevApplicationMinimalIdleHook()
and handles idle yieldingprvIdleTask()
has the added responsibility of callingprvCheckTasksWaitingTermination()
andvApplicationIdleHook()
.- One
prvIdleTask()
is created, the remaining idle tasks areprvMinimalIdleTask()
- The created idle tasks are all unpinned.
Changes Made:
- Idle tasks were now pinned in order to be compatible with ESP-IDF
- Currently,
vApplicationIdleHook()
andvApplicationMinimalIdleHook()
both just callesp_vApplicationIdleHook()
in order to feed the task watchdog.
Todo:
- Remove the pinning of idle tasks.
- Find some way to handle how FPU register contexts are cleaned up on task deletion. Idle tasks are no longer pinned, thus we need to handle the case where FPU cleanup is triggered across cores.
- Check if unpinned idle tasks will affect the feeding of the idle watchdog timer.
- Find some way to integrate TLSP Deletion Callbacks to the idle tasks.
- Check if tickless Idle (and automatic light sleep) works in SMP FreeRTOS
- Separate out
esp_vApplicationIdleHook()
from the SMP FreeRTOS Idle hooks for the following reasons:- Users may want to provide their own
vApplicationIdleHook()
andvApplicationMinimalIdleHook()
, thus IDF should not use those identifiers prvIdleTask()
will call bothvApplicationIdleHook()
andvApplicationMinimalIdleHook()
, thus the watchdog is fed twice.
- Users may want to provide their own
- Add support for
configIDLE_SHOULD_YIELD
- See if TLSP Deletion Callbacks can be upstreamed as a new feature
Tick Interrupt
IDF FreeRTOS:
- All cores have their own tick interrupts (same frequency but can be out of phase)
- Each core calls
xTaskIncrementTick()
independently.xTaskIncrementTick()
has been modified as such:- Core 0 is responsible for time keeping (incrementing tick count and unblocking timed out tasks)
- Each core independently checks for time slicing, and calls
vApplicationTickHook()
- There's also an additional
esp_vApplicationTickHook()
used to feed the interrupt watchdog.
SMP FreeRTOS:
- Only core 0 calls
xTaskIncrementTick()
. Thus core 0 is solely responsible for:- Time keeping (increment ticks and unblocking timed out tasks)
- Time slicing (calls
prvYieldCore()
on each core that needs to yield) - Calls
vApplicationTickHook()
- Increments pended ticks when the scheduler is suspended
Changes Made:
- Each core still has a tick interrupt (to call
esp_vApplicationTickHook()
). But only core 0 will callxTaskIncrementTick()
Todo:
- See if we can find another way to implement the interrupt watchdog. Requiring each core to interrupt just to feed the watchdog loses the benefit of having core 0 handling the time slicing of all cores.
Additional API
IDF FreeRTOS added several APIs. These are copied over to SMP FreeRTOS to maintain compatibility. These API can either be:
- Upstreamed to SMP FreeRTOS officially
- Moved to
freertos_tasks_c_additions.h
- Removed from IDF entirely
xTaskCreatePinnedToCore()
/xTaskCreateStaticPinnedToCore()
- Used to create a task with a preset affinity on creation
- When a task can only run on a particular core, this function saves the need of adding logic to:
- Disabling preemption on all cores
- Setting the created task's affinity
- Reenabling preemption on all cores.
- Check if this (or something similar) can be upstreamed
vTaskSetThreadLocalStoragePointerAndDelCallback()
- Used to set the TLSP deletion callbacks.
xTaskGetCurrentTaskHandleForCPU()
- Convenience function to the get current task of a particular CPU
- Check if this can be upstreamed
xTaskGetIdleTaskHandleForCPU()
- Convenience function to the get the idle task handle for a particular CPU
- Can be entirely replaced with
xTaskGetIdleTaskHandle()
xTaskGetAffinity()
- Returns what core a task is pinned to, and not an affinity mask.
- Can be entirely replaced with
vTaskCoreAffinityGet()