esp-idf/components/freertos/FreeRTOS-Kernel/portable/port_systick.c
Darian Leung 8b98e4e3b8 freertos(IDF): Restore vanilla call behavior on xTaskIncrementTick() and vTaskSwitchContext()
Due to SMP, critical sections have been added to xTaskIncrementTick() and
vTaskSwitchContext() (to take the xKernelLock). However, this is technically
not necessary when building for single-core as FreeRTOS expect these funcitons
to be called with interrupts already disabled.

This commit makes the critical secitons in those functions depend on
"configNUM_CORES > 1", and ensures that interrupts are disabled when calling
those functions. This effectively restores the vanilla behavior for these
functions when building for single-core.
2022-11-16 15:50:21 +08:00

211 lines
7.0 KiB
C

/*
* SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"
#include "esp_intr_alloc.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_private/systimer.h"
#include "esp_private/periph_ctrl.h"
#include "sdkconfig.h"
#ifdef CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER
#include "soc/periph_defs.h"
#include "hal/systimer_hal.h"
#include "hal/systimer_ll.h"
#endif
#ifdef CONFIG_PM_TRACE
#include "esp_private/pm_trace.h"
#endif //CONFIG_PM_TRACE
BaseType_t xPortSysTickHandler(void);
#ifdef CONFIG_FREERTOS_SYSTICK_USES_CCOUNT
extern void _frxt_tick_timer_init(void);
extern void _xt_tick_divisor_init(void);
#ifdef CONFIG_FREERTOS_CORETIMER_0
#define SYSTICK_INTR_ID (ETS_INTERNAL_TIMER0_INTR_SOURCE+ETS_INTERNAL_INTR_SOURCE_OFF)
#endif
#ifdef CONFIG_FREERTOS_CORETIMER_1
#define SYSTICK_INTR_ID (ETS_INTERNAL_TIMER1_INTR_SOURCE+ETS_INTERNAL_INTR_SOURCE_OFF)
#endif
/**
* @brief Initialize CCONT timer to generate the tick interrupt
*
*/
void vPortSetupTimer(void)
{
/* Init the tick divisor value */
_xt_tick_divisor_init();
_frxt_tick_timer_init();
}
#elif CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER
_Static_assert(SOC_CPU_CORES_NUM <= SOC_SYSTIMER_ALARM_NUM - 1, "the number of cores must match the number of core alarms in SYSTIMER");
void SysTickIsrHandler(void *arg);
static uint32_t s_handled_systicks[portNUM_PROCESSORS] = { 0 };
#define SYSTICK_INTR_ID (ETS_SYSTIMER_TARGET0_EDGE_INTR_SOURCE)
/**
* @brief Set up the systimer peripheral to generate the tick interrupt
*
* Both timer alarms are configured in periodic mode.
* It is done at the same time so SysTicks for both CPUs occur at the same time or very close.
* Shifts a time of triggering interrupts for core 0 and core 1.
*/
void vPortSetupTimer(void)
{
unsigned cpuid = xPortGetCoreID();
#ifdef CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3
const unsigned level = ESP_INTR_FLAG_LEVEL3;
#else
const unsigned level = ESP_INTR_FLAG_LEVEL1;
#endif
/* Systimer HAL layer object */
static systimer_hal_context_t systimer_hal;
/* set system timer interrupt vector */
ESP_ERROR_CHECK(esp_intr_alloc(ETS_SYSTIMER_TARGET0_EDGE_INTR_SOURCE + cpuid, ESP_INTR_FLAG_IRAM | level, SysTickIsrHandler, &systimer_hal, NULL));
if (cpuid == 0) {
periph_module_enable(PERIPH_SYSTIMER_MODULE);
systimer_hal_init(&systimer_hal);
systimer_hal_tick_rate_ops_t ops = {
.ticks_to_us = systimer_ticks_to_us,
.us_to_ticks = systimer_us_to_ticks,
};
systimer_hal_set_tick_rate_ops(&systimer_hal, &ops);
systimer_ll_set_counter_value(systimer_hal.dev, SYSTIMER_LL_COUNTER_OS_TICK, 0);
systimer_ll_apply_counter_value(systimer_hal.dev, SYSTIMER_LL_COUNTER_OS_TICK);
for (cpuid = 0; cpuid < SOC_CPU_CORES_NUM; cpuid++) {
systimer_hal_counter_can_stall_by_cpu(&systimer_hal, SYSTIMER_LL_COUNTER_OS_TICK, cpuid, false);
}
for (cpuid = 0; cpuid < portNUM_PROCESSORS; ++cpuid) {
uint32_t alarm_id = SYSTIMER_LL_ALARM_OS_TICK_CORE0 + cpuid;
/* configure the timer */
systimer_hal_connect_alarm_counter(&systimer_hal, alarm_id, SYSTIMER_LL_COUNTER_OS_TICK);
systimer_hal_set_alarm_period(&systimer_hal, alarm_id, 1000000UL / CONFIG_FREERTOS_HZ);
systimer_hal_select_alarm_mode(&systimer_hal, alarm_id, SYSTIMER_ALARM_MODE_PERIOD);
systimer_hal_counter_can_stall_by_cpu(&systimer_hal, SYSTIMER_LL_COUNTER_OS_TICK, cpuid, true);
if (cpuid == 0) {
systimer_hal_enable_alarm_int(&systimer_hal, alarm_id);
systimer_hal_enable_counter(&systimer_hal, SYSTIMER_LL_COUNTER_OS_TICK);
#ifndef CONFIG_FREERTOS_UNICORE
// SysTick of core 0 and core 1 are shifted by half of period
systimer_hal_counter_value_advance(&systimer_hal, SYSTIMER_LL_COUNTER_OS_TICK, 1000000UL / CONFIG_FREERTOS_HZ / 2);
#endif
}
}
} else {
uint32_t alarm_id = SYSTIMER_LL_ALARM_OS_TICK_CORE0 + cpuid;
systimer_hal_enable_alarm_int(&systimer_hal, alarm_id);
}
}
/**
* @brief Systimer interrupt handler.
*
* The Systimer interrupt for SysTick works in periodic mode no need to calc the next alarm.
* If a timer interrupt is ever serviced more than one tick late, it is necessary to process multiple ticks.
*/
IRAM_ATTR void SysTickIsrHandler(void *arg)
{
uint32_t cpuid = xPortGetCoreID();
systimer_hal_context_t *systimer_hal = (systimer_hal_context_t *)arg;
#ifdef CONFIG_PM_TRACE
ESP_PM_TRACE_ENTER(TICK, cpuid);
#endif
uint32_t alarm_id = SYSTIMER_LL_ALARM_OS_TICK_CORE0 + cpuid;
do {
systimer_ll_clear_alarm_int(systimer_hal->dev, alarm_id);
uint32_t diff = systimer_hal_get_counter_value(systimer_hal, SYSTIMER_LL_COUNTER_OS_TICK) / systimer_ll_get_alarm_period(systimer_hal->dev, alarm_id) - s_handled_systicks[cpuid];
if (diff > 0) {
if (s_handled_systicks[cpuid] == 0) {
s_handled_systicks[cpuid] = diff;
diff = 1;
} else {
s_handled_systicks[cpuid] += diff;
}
do {
xPortSysTickHandler();
} while (--diff);
}
} while (systimer_ll_is_alarm_int_fired(systimer_hal->dev, alarm_id));
#ifdef CONFIG_PM_TRACE
ESP_PM_TRACE_EXIT(TICK, cpuid);
#endif
}
#endif // CONFIG_FREERTOS_SYSTICK_USES_CCOUNT
extern void esp_vApplicationTickHook(void);
/**
* @brief Handler of SysTick
*
* The function is called from:
* - _frxt_timer_int for xtensa with CONFIG_FREERTOS_SYSTICK_USES_CCOUNT
* - SysTickIsrHandler for xtensa with CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER
* - SysTickIsrHandler for riscv
*/
BaseType_t xPortSysTickHandler(void)
{
#if configBENCHMARK
portbenchmarkIntLatency();
#endif //configBENCHMARK
traceISR_ENTER(SYSTICK_INTR_ID);
// Call IDF Tick Hook
esp_vApplicationTickHook();
// Call FreeRTOS Increment tick function
BaseType_t xSwitchRequired;
#if ( configNUM_CORES > 1 )
/*
For SMP, xTaskIncrementTick() will internally enter a critical section. But only core 0 calls xTaskIncrementTick()
while core 1 should call xTaskIncrementTickOtherCores().
*/
if (xPortGetCoreID() == 0) {
xSwitchRequired = xTaskIncrementTick();
} else {
xSwitchRequired = xTaskIncrementTickOtherCores();
}
#else // configNUM_CORES > 1
/*
Vanilla (single core) FreeRTOS expects that xTaskIncrementTick() cannot be interrupted (i.e., no nested interrupts).
Thus we have to disable interrupts before calling it.
*/
UBaseType_t uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
xSwitchRequired = xTaskIncrementTick();
portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);
#endif
// Check if yield is required
if (xSwitchRequired != pdFALSE) {
portYIELD_FROM_ISR();
} else {
traceISR_EXIT();
}
return xSwitchRequired;
}