esp-idf/components/freertos/FreeRTOS-Kernel/idf_changes.md

9.3 KiB

ESP-IDF Changes

This document is used to track all changes made the to FreeRTOS V10.5.1 source code when adding dual core SMP support or IDF additional features.

Todo: Add these to ESP-IDF docs once v10.5.1 becomes default kernel (IDF-8203)

License Headers

  • Added SPDX-FileCopyrightText and SPDX-FileContributor tags to all files to pass ESP-IDF pre-commit checks.
  • Changed kernel version tag to FreeRTOS Kernel V10.5.1 (ESP-IDF SMP modified) in all files to indicate files are different from upstream V10.5.1.

Omitted Features

  • Removed croutine.c and croutine.h

Changes from Upstream Main Branch not Included in v10.5.1

  • Added ...GetStaticBuffers functions that were upstreamed but not included in v10.5.1

Kernel SMP Changes

List of changes to the Vanilla FreeRTOS V10.5.1 kernel in order to support dual-core SMP

Scheduling Behavior Changes

  • The kernel now executes two tasks concurrently
  • The kernel now creates two IDLE tasks (pinned to each core)
  • Tasks can be pinned to either core, or have no affinity (can run on both cores)
  • Each core receives a tick interrupt, but only core 0 increments the tick count and unblocks timed out tasks
    • Core 0 calls xTaskIncrementTick()
    • Core 1 calls xTaskIncrementTickOtherCores()
  • Each core independently calls vTaskSwitchContext() to pick the highest priority task it can currently run
    • In single-core scheduling algorithm taskSELECT_HIGHEST_PRIORITY_TASK() unchanged
    • In SMP, prvSelectHighestPriorityTaskSMP() is called. This will select the highest priority ready state task that...
      • Has a compatible core affinity
      • Is not being run by another core
  • Each core can suspend scheduling independently (i.e., vTaskSuspendAll())

Configuration

Following configurations have been added

  • Added configNUMBER_OF_CORES to specify the number of cores to build. Can be 1 for vanilla, or 2 for SMP, error otherwise
  • Disable configUSE_PORT_OPTIMISED_TASK_SELECTION for SMP

Data Structure Changes (tasks.c)

The following data fields have been expanded to have configNUMBER_OF_CORES copies:

  • pxCurrentTCBs: Each core now has its own currently running task
  • xPendingReadyList: Each core has its own list to pend ready tasks if the scheduler is suspended on the core
  • xYieldPending: Each core has its own flag to track whether it has a pending yield
  • xIdleTaskHandle: Each core now has its own idle task
  • uxSchedulerSuspended: Each core can independently suspend scheduling on its core
  • ulTaskSwitchedInTime: Each core tracks its own "task switched in" time

Their access is now indexed by a xCoreID if in SMP, or set to 0 in single core.

The following data structures have been added:

  • TCB_t.xCoreID: All tasks now store their core affinity in a TCB member. Always set to 0 in single-core

API Additions

The following APIs have been added to support SMP

  • xTaskCreatePinnedToCore() and xTaskCreateStaticPinnedToCore() to create tasks with a core affinity
    • In single-core, core affinity is ignored. Same behavior as xTaskCreate()
  • xTaskGetCoreID() to get a task's affinity
  • Add ForCore() versions of the following API
    • xTaskGetIdleTaskHandleForCore()
    • xTaskGetCurrentTaskHandleForCore()
    • ulTaskGetIdleRunTimeCounterForCore()

API Modifications

Added the following macros that abstract away single-core and SMP differences:

  • taskYIELD_CORE() triggers a particular core to yield
  • taskIS_YIELD_REQUIRED()/taskIS_YIELD_REQUIRED_USING_PRIORITY() check if current core requires a yield after a task is unblocked
  • taskIS_AFFINITY_COMPATIBLE() check if a task has compatible affinity
  • taskIS_CURRENTLY_RUNNING()/taskIS_CURRENTLY_RUNNING_ON_CORE() checks if a task is running on either core
  • taskCAN_BE_SCHEDULED() checks if an unblocked task can be scheduled on any core
  • taskIS_SCHEDULER_SUSPENDED() checks if the scheduler on the current core is suspended
  • taskSELECT_HIGHEST_PRIORITY_TASK() selects the highest priority task to execute for the current core
  • prvGetTCBFromHandle() updated in SMP to call xTaskGetCurrentTaskHandle() when the handle is NULL. Done so for thread safety (in case the current task switches cores at the same time).

The following functions were modified to accommodate SMP behavior:

  • prvInitialiseNewTask()
    • Added xCoreID argument to pin task on creation
    • For single-core, xCoreID is hard coded to 0
  • prvAddNewTaskToReadyList()
    • Checks if new task can be scheduled on core 1
  • vTaskDelete()
    • Checks if the deleted task is currently running on the other core.
    • If so, sends a yield to the other core.
  • vTaskPrioritySet()
    • Checks if the task is currently running on the both cores, and yields the appropriate core if so
  • vTaskSuspend()
    • Checks if the task is currently running on the other core, and yields the other core if so.
  • prvTaskIsTaskSuspended()
    • Checks the xPendingReadyList of both cores to see if a task is suspended
  • xTaskResumeAll()
    • Limit catching up of tick counts to core 0 (given only core 0 calls xTaskIncrementTick())
  • xTaskIncrementTick()
    • Limited to core 0
  • vTaskSwitchContext()
    • Switches context for current core
  • xTaskRemoveFromEventList()
    • Created SMP copy of the function
      • Checks if pxEventList has already been emptied by the other core before removing
      • Checks if task can be scheduled on both cores, adds it to the appropriate core's pending list if it can't be scheduled.
  • vTaskRemoveFromUnorderedEventList()
    • In SMP, check if the task can be scheduled before adding it to the appropriate list. Whereas in single-core, the scheduler is always suspended thus the unblocked task always goes onto the pending ready list.
  • eTaskConfirmSleepModeStatus()
    • Updated logic to determine whether sleep is possible in SMP by checking the status of both cores.
  • prvCheckTasksWaitingTermination()
    • Updated logic so that we don't delete tasks on xTasksWaitingTermination which are still currently running on the other core.
  • xTaskGetCurrentTaskHandle()
    • In SMP, the function will now disables interrupts to ensure that the calling task does not switch cores while fetching the current core's TCB.
  • xTaskGetSchedulerState()
    • In SMP, the function now disables interrupts to ensure that the calling task does not switch cores while checking its own copy of uxSchedulerSuspended.
  • prvAddCurrentTaskToDelayedList()
    • Added extra check to see if current blocking task has already been deleted by the other core.

Critical Section Changes

  • Granular Locks: The following objects are now given their own spinlocks
    • Kernel objects (i.e., tasks.c): xKernelLock
    • Each queue: xQueueLock
    • Queue Registry: xQueueRegistryLock
    • Each event group: xEventGroupLock
    • Each stream buffer: xStreamBufferLock
    • All timers: xTimerLock
  • Critical sections now target the appropriate spinlocks
  • Added missing critical sections for SMP (see ..._SMP_ONLY() critical section calls)
  • Queues no longer use queue locks (see queueUSE_LOCKS)
    • Queues now just use critical sections and skips queue locking
    • Queue functions can now execute within a single critical section block

Single Core Differences

List of differences between Vanilla FreeRTOS V10.5.1 and building the dual-core SMP kernel with congigNUMBER_OF_CORES == 1.

  • prvAddNewTaskToReadyList()
    • Extended critical section so that SMP can check for yields while still inside critical section
  • vTaskStepTick()
    • Extended critical section so that SMP can access xTickCount while still inside critical section

Header File & Doxygen Changes

List of changes made to Vanilla FreeRTOS V10.5.1 header files to allow for building in ESP-IDF documentation build system.

  • Removed leading header name line (e.g., xxx.h) in doxygen comment blocks. For example:

    /**
     * xxx.h
     *
     * Documentation from some func
     */
    void some_func(void);
    
  • Removed leading @code{c} blocks in containing redundant function prototypes. For example:

    /**
     * @code{c}
     * void some_func(int var_a, int var_b);
     * @endcode
     *
     * Documentation from some func
     */
    void some_func(int var_a, int var_b);
    
  • Added /** @cond !DOC_EXCLUDE_HEADER_SECTION */ and /** @endcond */ labels to exclude various doxygen sections from being included into documentation builds. These excluded sections include:

    • In doxygen blocks that describe multiple related set of functions/macros, only the function/macro that matches the doxygen blocks parameters is included. For example:
      /**
       * Description that covers both some_func() and some_func_extra()
       *
       * @param var_a var_a description
       * @param var_b var_b description
       */
      /** @cond !DOC_EXCLUDE_HEADER_SECTION */
      #define some_func(var_a)                #define some_func_generic(var_a, NULL)
      /** @endcond */
      #define some_func_extra(var_a, var_b)   #define some_func_generic(var_a, var_b)
      
    • In functions/macros that are not meant to be directly called by users (i.e., internal), such as the various Generic variants of functions
  • Some types/functions/macros are manually documented, thus are documented with regular comment blocks (i.e., /* */) instead of doxygen comment blocks (i.e., /** */). Some of these blocks are changed into doxygen blocks.