feat(ipc_isr): adds IPC ISR safe API for other CPU stall API

This commit is contained in:
Konstantin Kondrashov
2026-05-29 08:26:06 +03:00
parent a2d6680477
commit 672736304c
10 changed files with 499 additions and 64 deletions

View File

@@ -1,11 +1,13 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_err.h"
#include "sdkconfig.h"
#ifdef __cplusplus
@@ -39,11 +41,14 @@ typedef void (*esp_ipc_isr_func_t)(void* arg);
* - in C or assembly for RISCV chips (such as ESP32P4).
*
* @note This function is not available in single-core mode.
* @note This function can be called even if the other core is stalled
* (either via esp_ipc_isr_stall_other_cpu() or esp_ipc_isr_stall_other_cpu_safe()).
* This allows safely stalling the other CPU and executing one or more callbacks before releasing it.
*
* @param[in] func Pointer to a function of type void func(void* arg) to be executed
* @param[in] arg Arbitrary argument of type void* to be passed into the function
*/
void esp_ipc_isr_call(esp_ipc_isr_func_t func, void* arg) ;
void esp_ipc_isr_call(esp_ipc_isr_func_t func, void* arg);
/**
* @brief Execute an ISR callback on the other CPU
@@ -58,6 +63,9 @@ void esp_ipc_isr_call(esp_ipc_isr_func_t func, void* arg) ;
* the callback completes.
*
* @note This function is not available in single-core mode.
* @note This function can be called even if the other core is stalled
* (either via esp_ipc_isr_stall_other_cpu() or esp_ipc_isr_stall_other_cpu_safe()).
* This allows safely stalling the other CPU and executing one or more callbacks before releasing it.
*
* @param[in] func Pointer to a function of type void func(void* arg) to be executed
* @param[in] arg Arbitrary argument of type void* to be passed into the function
@@ -71,20 +79,52 @@ void esp_ipc_isr_call_blocking(esp_ipc_isr_func_t func, void* arg);
#define esp_ipc_isr_asm_call_blocking(func, arg) esp_ipc_isr_call_blocking(func, arg)
/**
* @brief Stall the other CPU
* @brief Stall the other CPU unconditionally
*
* This function will stall the other CPU. The other CPU is stalled by busy-waiting in the context of a High Priority
* Interrupt. The other CPU will not be resumed until esp_ipc_isr_release_other_cpu() is called.
* This function forces the other CPU to enter a busy-wait loop within a High Priority Interrupt context,
* effectively stalling its execution until esp_ipc_isr_release_other_cpu() is called.
*
* - This function is internally implemented using IPC ISR
* - This function is used for DPORT workaround.
* - If the stall feature is paused using esp_ipc_isr_stall_pause(), this function will have no effect
* Typical use cases include:
* - DPORT workaround (e.g., DPORT or APB registers) on dual-core ESP32 chips prior to v2.0.
* - Scenarios where the state of the other CPU does not need to be checked, stalling unconditionally.
*
* @note This function is not available in single-core mode.
* @note It is the caller's responsibility to avoid deadlocking on spinlocks
* Implementation details:
* - The function is initialized after FreeRTOS startup.
* - When invoked, it signals the other CPU to enter a high-priority interrupt and remain stalled.
* - If the other CPU is already in a high-priority interrupt, it is considered stalled.
* - The other CPU will remain stalled until esp_ipc_isr_release_other_cpu() is called.
* - This function does not check the current state (e.g., critical section or ISR) of the other CPU.
* To avoid potential deadlocks on spinlocks, use esp_ipc_isr_stall_other_cpu_safe() instead.
* - If the stall feature is paused via esp_ipc_isr_stall_pause(), this function has no effect.
*
* @note Not available in single-core mode.
* @note The caller is responsible for ensuring no deadlocks on spinlocks occur. Use esp_ipc_isr_stall_other_cpu_safe() for safer operation.
*/
void esp_ipc_isr_stall_other_cpu(void);
/**
* @brief Safely stall the other CPU
*
* Attempts to stall the other CPU only if it is not currently in a critical section or ISR context.
* If the other CPU is in a critical section or ISR, the function will return an error.
*
* This helps to prevent potential deadlocks when both CPUs may access shared resources/spinlocks.
*
* @note This function is intended for scenarios where safe stalling is required and the state of the other CPU must be checked.
*
* @return
* - ESP_OK: The other CPU was successfully stalled.
* - ESP_ERR_NOT_ALLOWED: The other CPU is in a critical section or ISR context and cannot be stalled.
*/
esp_err_t esp_ipc_isr_stall_other_cpu_safe(void);
/**
* @brief Check whether the other CPU is currently stalled
*
* @return true if the other CPU has entered the IPC ISR stall loop, false otherwise.
*/
bool esp_ipc_isr_is_other_cpu_stalled(void);
/**
* @brief Release the other CPU
*
@@ -109,7 +149,7 @@ void esp_ipc_isr_stall_pause(void);
/**
* @brief Abort a CPU stall
*
* This function will abort any stalling routine of the other CPU due to a pervious call to
* This function will abort any stalling routine of the other CPU due to a previous call to
* esp_ipc_isr_stall_other_cpu(). This function aborts the stall in a non-recoverable manner, thus should only be called
* in case of a panic().
*
@@ -128,6 +168,8 @@ void esp_ipc_isr_stall_resume(void);
#else // CONFIG_ESP_IPC_ISR_ENABLE
#define esp_ipc_isr_stall_other_cpu()
#define esp_ipc_isr_stall_other_cpu_safe() (ESP_OK)
#define esp_ipc_isr_is_other_cpu_stalled() (false)
#define esp_ipc_isr_release_other_cpu()
#define esp_ipc_isr_stall_pause()
#define esp_ipc_isr_stall_abort()

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -8,12 +8,53 @@
#include "sdkconfig.h"
#define ESP_IPC_ISR_ARGS_CMD_OFFSET 0
#define ESP_IPC_ISR_ARGS_FUNC_OFFSET 4
#define ESP_IPC_ISR_ARGS_ARG_OFFSET 8
#define ESP_IPC_ISR_ARGS_RET_ADDR_OFFSET 12
#define ESP_IPC_ISR_ARGS_INTERRUPTED_CONTEXT_UNSAFE_OFFSET 16
#ifndef __ASSEMBLER__
#include <stddef.h>
#include <stdint.h>
#include "esp_assert.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef CONFIG_ESP_IPC_ISR_ENABLE
/**
* @brief IPC ISR command types.
*
* Enumerates the possible commands that caller CPU can issue to the stalled CPU.
*/
typedef enum {
ESP_IPC_ISR_CMD_RESET_STATE = 0, /**< Reset the command state. */
ESP_IPC_ISR_CMD_FINISH = 1, /**< Signal that the ISR operation is finished. */
} esp_ipc_isr_cmd_t;
/**
* @brief Arguments structure for IPC ISR functions.
*
* This structure holds the function pointer and its argument to be used in callbacks.
*/
typedef struct {
esp_ipc_isr_cmd_t cmd; /**< Command to control the IPC ISR operation. */
void (*func)(void*); /**< Pointer to the function to be called in the ISR. */
void* arg; /**< Argument to be passed to the function. */
void* save_ret_addr; /**< Address to save the return address for the ISR (used by xtensa chips). */
uint32_t interrupted_context_unsafe; /**< Non-zero if the interrupted CPU context is unsafe to stall. */
} esp_ipc_isr_args_t;
ESP_STATIC_ASSERT(offsetof(esp_ipc_isr_args_t, cmd) == ESP_IPC_ISR_ARGS_CMD_OFFSET, "Unexpected IPC ISR cmd offset");
ESP_STATIC_ASSERT(offsetof(esp_ipc_isr_args_t, func) == ESP_IPC_ISR_ARGS_FUNC_OFFSET, "Unexpected IPC ISR func offset");
ESP_STATIC_ASSERT(offsetof(esp_ipc_isr_args_t, arg) == ESP_IPC_ISR_ARGS_ARG_OFFSET, "Unexpected IPC ISR arg offset");
ESP_STATIC_ASSERT(offsetof(esp_ipc_isr_args_t, save_ret_addr) == ESP_IPC_ISR_ARGS_RET_ADDR_OFFSET, "Unexpected IPC ISR return address offset");
ESP_STATIC_ASSERT(offsetof(esp_ipc_isr_args_t, interrupted_context_unsafe) == ESP_IPC_ISR_ARGS_INTERRUPTED_CONTEXT_UNSAFE_OFFSET,
"Unexpected IPC ISR interrupted context unsafe offset");
/**
* @brief Initialize the IPC ISR feature, must be called for each CPU
*
@@ -35,3 +76,5 @@ void esp_ipc_isr_init(void);
#ifdef __cplusplus
}
#endif
#endif // __ASSEMBLER__

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -35,6 +35,8 @@ esp_ipc_isr_handler:
/* MIE is cleared, so nested interrupts are disabled */
call esp_ipc_isr_record_interrupted_context
/* Reset isr interrupt flags */
li a1, SYSTEM_CPU_INTR_FROM_CPU_2_REG
csrr a0, mhartid # Get CORE_ID

View File

@@ -1,13 +1,56 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "stdint.h"
#include "soc/interrupt_reg.h"
#include "soc/soc_caps.h"
#include "esp_ipc_isr.h"
#include "esp_private/esp_ipc_isr.h"
#include "esp_private/esp_system_attr.h"
#include "soc/soc.h"
#include "riscv/csr.h"
void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_waiting_for_finish_cmd(void* ipc_isr_finish_cmd)
#if SOC_INT_CLIC_SUPPORTED
#include "esp_private/interrupt_clic.h"
#endif
#define ESP_IPC_ISR_RISCV_INTR_ENABLE_THRESH 1
extern volatile esp_ipc_isr_args_t esp_ipc_isr_stall_args;
extern volatile uint32_t esp_ipc_isr_stall_fl;
void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_record_interrupted_context(void)
{
while (*(volatile uint32_t *)ipc_isr_finish_cmd == 0) { };
uint32_t unsafe = 0;
#if SOC_INT_CLIC_SUPPORTED
if (rv_utils_get_interrupt_threshold() != 0) {
unsafe = 1;
}
uint32_t mpil = (RV_READ_CSR(mcause) >> 16) & 0xff;
if (BYTE_TO_NLBITS(mpil) != 0) {
unsafe = 1;
}
#else
if (REG_READ(INTERRUPT_CURRENT_CORE_INT_THRESH_REG) > ESP_IPC_ISR_RISCV_INTR_ENABLE_THRESH) {
unsafe = 1;
}
#endif
esp_ipc_isr_stall_args.interrupted_context_unsafe = unsafe;
}
void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_waiting_for_finish_cmd(void* arg)
{
esp_ipc_isr_stall_fl = 1;
while (esp_ipc_isr_stall_args.cmd == ESP_IPC_ISR_CMD_RESET_STATE) {
if (esp_ipc_isr_stall_args.func != NULL) {
esp_ipc_isr_stall_args.func(esp_ipc_isr_stall_args.arg);
esp_ipc_isr_stall_args.func = NULL;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -12,6 +12,7 @@
#include "sdkconfig.h"
#include "soc/soc.h"
#include "soc/dport_reg.h"
#include "esp_private/esp_ipc_isr.h"
/* High-priority interrupt - IPC_ISR handler */
@@ -22,6 +23,7 @@
#define LX_INTR_A3_OFFSET 8
#define LX_INTR_A4_OFFSET 12
#define XT_REG_EXCSAVE_X XT_REG_EXCSAVE_5
#define XT_REG_EPS_X XT_REG_EPS_5
#define RFI_X 5
#elif CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4
@@ -32,6 +34,7 @@
#define LX_INTR_A3_OFFSET 8
#define LX_INTR_A4_OFFSET 12
#define XT_REG_EXCSAVE_X XT_REG_EXCSAVE_4
#define XT_REG_EPS_X XT_REG_EPS_4
#define RFI_X 4
#endif /* CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */
@@ -58,6 +61,20 @@ esp_ipc_isr_handler:
rsr a2, XT_REG_EXCSAVE_X
s32i a2, a0, LX_INTR_A0_OFFSET
/*
* Snapshot the interrupted context before this handler changes PS.
* A non-zero INTLEVEL means the other CPU was in an ISR or an
* interrupt-masked section, so safe stall must retry later.
*/
movi a0, esp_ipc_isr_stall_args /* a0 = base address of stall args struct */
rsr a3, XT_REG_EPS_X /* a3 = interrupted PS (processor status) */
extui a3, a3, PS_INTLEVEL_SHIFT, 4 /* a3 = extracted PS.INTLEVEL field */
beqz a3, .Lipc_isr_xtensa_context_unsafe_done /* if INTLEVEL == 0, leave unsafe value as 0 */
movi a3, 1 /* else interrupted context is unsafe to stall */
.Lipc_isr_xtensa_context_unsafe_done:
s32i a3, a0, ESP_IPC_ISR_ARGS_INTERRUPTED_CONTEXT_UNSAFE_OFFSET /* store interrupted-context unsafe value */
memw /* enforce memory ordering/visibility before continuing */
/* disable nested interrupts */
/* PS.EXCM is changed from 1 to 0 . It allows using usually exception handler instead of the Double exception handler. */
/* PS_UM = 1 */

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -9,11 +9,20 @@
#include <xtensa/config/system.h>
#include <xtensa/hal.h>
#include "sdkconfig.h"
#include "esp_private/esp_ipc_isr.h"
/* esp_ipc_isr_waiting_for_finish_cmd(void* finish_cmd)
/* void esp_ipc_isr_waiting_for_finish_cmd(void* arg)
*
* It should be called by the CALLX0 command from the handler of High-priority interrupt.
* Only these registers [a2, a3, a4] can be used here.
* Only a2, a3, a4 are used/clobbered here.
*
* typedef struct {
* uint32_t cmd; // +0
* void* func; // +4
* void* arg; // +8
* void* save_ret_addr; // +12
* uint32_t interrupted_context_unsafe; // +16
* } esp_ipc_isr_args_t;
*/
#if CONFIG_ESP_SYSTEM_IN_IRAM
.section .iram1, "ax"
@@ -22,11 +31,51 @@
.global esp_ipc_isr_waiting_for_finish_cmd
.type esp_ipc_isr_waiting_for_finish_cmd, @function
// Args:
// a2 - finish_cmd (pointer on esp_ipc_isr_finish_cmd)
// a2 - arg
/* Must match C defines */
.equ ESP_IPC_ISR_CMD_RESET_STATE, 0
/* Field offsets */
.equ OFFS_CMD, ESP_IPC_ISR_ARGS_CMD_OFFSET
.equ OFFS_FUNC, ESP_IPC_ISR_ARGS_FUNC_OFFSET
.equ OFFS_ARG, ESP_IPC_ISR_ARGS_ARG_OFFSET
.equ OFFS_RET_ADDR, ESP_IPC_ISR_ARGS_RET_ADDR_OFFSET
.extern esp_ipc_isr_stall_args /* esp_ipc_isr_args_t */
.extern esp_ipc_isr_stall_fl
esp_ipc_isr_waiting_for_finish_cmd:
/* waiting for the finish command */
.check_finish_cmd:
movi a3, esp_ipc_isr_stall_fl
movi a4, 1
s32i a4, a3, 0
memw
l32i a3, a2, 0
beqz a3, .check_finish_cmd
.loop_top:
/* while (esp_ipc_isr_stall_args.cmd == RESET_STATE) */
memw
movi a3, esp_ipc_isr_stall_args
l32i a4, a3, OFFS_CMD
bnez a4, .ret_label /* cmd != 0 -> exit */
/* if (esp_ipc_isr_stall_args.func != NULL) */
l32i a4, a3, OFFS_FUNC /* a4 = func */
beqz a4, .loop_top
s32i a0, a3, OFFS_RET_ADDR
/* Call func(arg) with call0 ABI */
l32i a2, a3, OFFS_ARG /* a2 = arg (callx0 argument) */
callx0 a4 /* func(a2) */
/* esp_ipc_isr_stall_args.func = NULL; */
movi a3, esp_ipc_isr_stall_args
movi a4, 0
s32i a4, a3, OFFS_FUNC
memw
l32i a0, a3, OFFS_RET_ADDR
j .loop_top
.ret_label:
ret

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -21,8 +21,14 @@
static portMUX_TYPE s_ipc_isr_mux = portMUX_INITIALIZER_UNLOCKED;
uint32_t volatile esp_ipc_isr_start_fl; // the flag shows that it is about to run esp_ipc_func()
uint32_t volatile esp_ipc_isr_end_fl = 1; // the flag shows that esp_ipc_func() is done
uint32_t volatile esp_ipc_isr_stall_fl; // the flag shows that the other CPU entered the stall loop
esp_ipc_isr_func_t volatile esp_ipc_func; // the function which will be run in the ipc_isr context
void * volatile esp_ipc_func_arg; // the argument of esp_ipc_func()
esp_ipc_isr_args_t volatile esp_ipc_isr_stall_args; // the context for the stalled CPU
// This asm function is from esp_ipc_isr_routines.S.
// It is waiting for the finish_cmd command in a loop.
void esp_ipc_isr_waiting_for_finish_cmd(void* arg);
typedef enum {
STALL_STATE_IDLE = 0,
@@ -32,20 +38,22 @@ typedef enum {
static stall_state_t volatile s_stall_state = STALL_STATE_IDLE;
static int32_t volatile s_count_of_nested_calls[CONFIG_FREERTOS_NUMBER_OF_CORES] = { 0 };
static BaseType_t s_stored_interrupt_level;
static uint32_t volatile esp_ipc_isr_finish_cmd;
static bool volatile s_other_cpu_stalled = false;
/**
* @brief Type of calling
*/
typedef enum {
IPC_ISR_WAIT_FOR_START = 0, /*!< The caller is waiting for the start */
IPC_ISR_WAIT_FOR_END = 1, /*!< The caller is waiting for the end */
IPC_ISR_WAIT_FOR_START = (1 << 0), /*!< The caller is waiting for the start */
IPC_ISR_WAIT_FOR_END = (1 << 1), /*!< The caller is waiting for the end */
IPC_ISR_SAFE_STALL = (1 << 2), /*!< Safe stall mode: run the stall command loop and verify the stalled CPU context */
} esp_ipc_isr_wait_t;
#define IPC_ISR_ENTER_CRITICAL() portENTER_CRITICAL_SAFE(&s_ipc_isr_mux)
#define IPC_ISR_EXIT_CRITICAL() portEXIT_CRITICAL_SAFE(&s_ipc_isr_mux)
static void esp_ipc_isr_call_and_wait(esp_ipc_isr_func_t func, void* arg, esp_ipc_isr_wait_t wait_for);
static esp_err_t ipc_isr_call_and_wait(esp_ipc_isr_func_t func, void* arg, esp_ipc_isr_wait_t wait_for);
static esp_err_t ipc_isr_stall_other_cpu(esp_ipc_isr_wait_t wait_for);
/* Initializing IPC_ISR */
@@ -66,21 +74,39 @@ void esp_ipc_isr_init(void)
void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_call(esp_ipc_isr_func_t func, void* arg)
{
IPC_ISR_ENTER_CRITICAL();
esp_ipc_isr_call_and_wait(func, arg, IPC_ISR_WAIT_FOR_START);
if (s_other_cpu_stalled) {
/* If the other CPU is already stalled, wait for a previous call
to finish and initiate a new call */
while (esp_ipc_isr_stall_args.func != NULL) {}
esp_ipc_isr_stall_args.arg = arg;
esp_ipc_isr_stall_args.func = (void*)func;
// do not wait for the user's callback function to finish
} else {
ipc_isr_call_and_wait(func, arg, IPC_ISR_WAIT_FOR_START);
}
IPC_ISR_EXIT_CRITICAL();
}
void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_call_blocking(esp_ipc_isr_func_t func, void* arg)
{
IPC_ISR_ENTER_CRITICAL();
esp_ipc_isr_call_and_wait(func, arg, IPC_ISR_WAIT_FOR_END);
if (s_other_cpu_stalled) {
// If the other CPU is already stalled, wait for a previous call to finish and initiate a new call
while (esp_ipc_isr_stall_args.func != NULL) {}
esp_ipc_isr_stall_args.arg = arg;
esp_ipc_isr_stall_args.func = (void*)func;
/* Wait for the user's callback function to complete.
The esp_ipc_isr_waiting_for_finish_cmd function will reset
the func field in esp_ipc_isr_stall_args once the callback
has finished, indicating that the other CPU is ready to accept
new callbacks. */
while (esp_ipc_isr_stall_args.func != NULL) {}
} else {
ipc_isr_call_and_wait(func, arg, IPC_ISR_WAIT_FOR_END);
}
IPC_ISR_EXIT_CRITICAL();
}
// This asm function is from esp_ipc_isr_routines.S.
// It is waiting for the finish_cmd command in a loop.
void esp_ipc_isr_waiting_for_finish_cmd(void* finish_cmd);
/*
* esp_ipc_isr_stall_other_cpu is used for:
* - stall other CPU,
@@ -92,13 +118,26 @@ void esp_ipc_isr_waiting_for_finish_cmd(void* finish_cmd);
*/
void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_stall_other_cpu(void)
{
#if CONFIG_FREERTOS_SMP
/*
Temporary workaround to prevent deadlocking on the SMP FreeRTOS kernel lock after stalling the other CPU.
See IDF-5257
*/
taskENTER_CRITICAL();
#endif
ipc_isr_stall_other_cpu(IPC_ISR_WAIT_FOR_START);
}
/*
* Stall the other CPU core only if it is in a "safe" state (not in a critical section or ISR).
* Returns ESP_OK if the stall was successful, or ESP_ERR_NOT_ALLOWED if the other core
* is currently in a critical section or ISR context.
*/
esp_err_t ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_stall_other_cpu_safe(void)
{
return ipc_isr_stall_other_cpu(IPC_ISR_WAIT_FOR_START | IPC_ISR_SAFE_STALL);
}
bool ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_is_other_cpu_stalled(void)
{
return s_other_cpu_stalled;
}
static esp_err_t ESP_SYSTEM_IRAM_ATTR ipc_isr_stall_other_cpu(esp_ipc_isr_wait_t wait_for)
{
if (s_stall_state == STALL_STATE_RUNNING) {
#if CONFIG_FREERTOS_SMP
BaseType_t intLvl = portDISABLE_INTERRUPTS();
@@ -109,9 +148,21 @@ void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_stall_other_cpu(void)
if (s_count_of_nested_calls[cpu_id]++ == 0) {
IPC_ISR_ENTER_CRITICAL();
s_stored_interrupt_level = intLvl;
esp_ipc_isr_finish_cmd = 0;
esp_ipc_isr_call_and_wait(&esp_ipc_isr_waiting_for_finish_cmd, (void*)&esp_ipc_isr_finish_cmd, IPC_ISR_WAIT_FOR_START);
return;
esp_ipc_isr_stall_args.cmd = ESP_IPC_ISR_CMD_RESET_STATE;
esp_ipc_isr_stall_args.func = NULL;
esp_ipc_isr_stall_args.arg = NULL;
esp_ipc_isr_stall_args.interrupted_context_unsafe = 1;
s_other_cpu_stalled = false;
esp_ipc_isr_stall_fl = 0;
esp_err_t error = ipc_isr_call_and_wait(esp_ipc_isr_waiting_for_finish_cmd, NULL, wait_for);
if (error != ESP_OK) {
esp_ipc_isr_release_other_cpu();
} else {
// Publish the stalled state only after the other CPU enters the stall loop.
while (!esp_ipc_isr_stall_fl) {};
s_other_cpu_stalled = true;
}
return error;
}
/* Interrupts are already disabled by the parent, we're nested here. */
@@ -120,7 +171,9 @@ void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_stall_other_cpu(void)
#else
portCLEAR_INTERRUPT_MASK_FROM_ISR(intLvl);
#endif
return ESP_OK;
}
return ESP_FAIL;
}
void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_release_other_cpu(void)
@@ -128,9 +181,11 @@ void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_release_other_cpu(void)
if (s_stall_state == STALL_STATE_RUNNING) {
const uint32_t cpu_id = xPortGetCoreID();
if (--s_count_of_nested_calls[cpu_id] == 0) {
esp_ipc_isr_finish_cmd = 1;
// Make sure end flag is cleared and esp_ipc_isr_waiting_for_finish_cmd is done.
s_other_cpu_stalled = false;
esp_ipc_isr_stall_args.cmd = ESP_IPC_ISR_CMD_FINISH;
// Make sure end flag is set and esp_ipc_isr_waiting_for_finish_cmd is done.
while (!esp_ipc_isr_end_fl) {};
esp_ipc_isr_stall_fl = 0;
IPC_ISR_EXIT_CRITICAL();
#if CONFIG_FREERTOS_SMP
portRESTORE_INTERRUPTS(s_stored_interrupt_level);
@@ -141,13 +196,6 @@ void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_release_other_cpu(void)
assert(0);
}
}
#if CONFIG_FREERTOS_SMP
/*
Temporary workaround to prevent deadlocking on the SMP FreeRTOS kernel lock after stalling the other CPU.
See IDF-5257
*/
taskEXIT_CRITICAL();
#endif
}
void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_stall_pause(void)
@@ -174,7 +222,7 @@ void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_stall_resume(void)
/* Private functions*/
static void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_call_and_wait(esp_ipc_isr_func_t func, void* arg, esp_ipc_isr_wait_t wait_for)
static esp_err_t ESP_SYSTEM_IRAM_ATTR ipc_isr_call_and_wait(esp_ipc_isr_func_t func, void* arg, esp_ipc_isr_wait_t wait_for)
{
const uint32_t cpu_id = xPortGetCoreID();
@@ -191,12 +239,35 @@ static void ESP_SYSTEM_IRAM_ATTR esp_ipc_isr_call_and_wait(esp_ipc_isr_func_t fu
esp_ipc_isr_port_int_trigger(!cpu_id);
// IPC_ISR handler will be called and `...isr_start` and `...isr_end` will be updated there
if (wait_for == IPC_ISR_WAIT_FOR_START) {
if (wait_for & IPC_ISR_WAIT_FOR_START) {
while (!esp_ipc_isr_start_fl) {};
} else {
// IPC_ISR_WAIT_FOR_END
}
if (wait_for & IPC_ISR_SAFE_STALL) {
/* The handler snapshots the interrupted context before publishing
* esp_ipc_isr_start_fl. A non-zero value means the other CPU was in an
* ISR or interrupt-masked section, including the small window where
* interrupts are already masked but FreeRTOS nesting counters are not
* updated yet.
*/
bool safe_to_continue = (esp_ipc_isr_stall_args.interrupted_context_unsafe == 0);
if (!safe_to_continue) {
return ESP_ERR_NOT_ALLOWED;
}
/* IPC_ISR_SAFE_STALL is used only with the stall command-loop
* callback. At this point, esp_ipc_isr_waiting_for_finish_cmd()
* is already running on the other CPU and keeps it blocked while
* this CPU checks whether the interrupted context was safe. If it
* was safe, return without waiting for callback completion: later
* IPC ISR stall APIs will either execute callbacks on the stalled
* CPU or release it from the loop.
*/
return ESP_OK;
} else if (wait_for & IPC_ISR_WAIT_FOR_END) {
// Blocking callers wait until the IPC ISR handler finishes the callback.
while (!esp_ipc_isr_end_fl) {};
}
return ESP_OK;
}
/* End private functions*/

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -66,6 +66,7 @@ TEST_CASE("Test ipc_isr blocking IPC function calls get_cycle_count_other_cpu",
}
static bool volatile s_stop;
static bool volatile s_other_cpu_in_critical_section;
static void task_asm(void *arg)
{
@@ -103,4 +104,133 @@ TEST_CASE("Test ipc_isr two tasks use IPC function calls", "[ipc]")
vSemaphoreDelete(exit_sema[0]);
vSemaphoreDelete(exit_sema[1]);
}
static bool volatile s_cmd_other_cpu_exit_critical_section;
static void other_cpu_task_spinlock(void *arg)
{
printf("other_cpu_task_spinlock start, taking critical section...\n");
portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED;
portENTER_CRITICAL(&spinlock);
s_other_cpu_in_critical_section = true;
while (s_cmd_other_cpu_exit_critical_section == false) { }
s_other_cpu_in_critical_section = false;
portEXIT_CRITICAL(&spinlock);
printf("other_cpu_task_spinlock end, exited critical section.\n");
xSemaphoreGive(*(SemaphoreHandle_t *) arg);
vTaskDelete(NULL);
}
static void other_cpu_task_task_critical(void *arg)
{
printf("other_cpu_task_task_critical start, taking critical section...\n");
#if CONFIG_FREERTOS_SMP
taskENTER_CRITICAL();
#else
portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED;
taskENTER_CRITICAL(&spinlock);
#endif
s_other_cpu_in_critical_section = true;
while (s_cmd_other_cpu_exit_critical_section == false) { }
s_other_cpu_in_critical_section = false;
#if CONFIG_FREERTOS_SMP
taskEXIT_CRITICAL();
#else
taskEXIT_CRITICAL(&spinlock);
#endif
printf("other_cpu_task_task_critical end, exited critical section.\n");
xSemaphoreGive(*(SemaphoreHandle_t *) arg);
vTaskDelete(NULL);
}
TEST_CASE("Test stall other CPU safe", "[ipc]")
{
SemaphoreHandle_t other_cpu_task_finished = xSemaphoreCreateBinary();
printf("Test start\n");
s_cmd_other_cpu_exit_critical_section = false;
s_other_cpu_in_critical_section = false;
xTaskCreatePinnedToCore(other_cpu_task_spinlock, "other_cpu_task", 4096, &other_cpu_task_finished, UNITY_FREERTOS_PRIORITY - 1, NULL, 1);
while (!s_other_cpu_in_critical_section) {
vTaskDelay(1);
}
TEST_ASSERT_EQUAL(ESP_ERR_NOT_ALLOWED, esp_ipc_isr_stall_other_cpu_safe());
s_cmd_other_cpu_exit_critical_section = true;
while (esp_ipc_isr_stall_other_cpu_safe() != ESP_OK) {
vTaskDelay(1);
}
TEST_ASSERT_TRUE(esp_ipc_isr_is_other_cpu_stalled());
int stalled_core_id = -1;
esp_ipc_isr_call_blocking(esp_test_ipc_isr_get_other_core_id, &stalled_core_id);
TEST_ASSERT_EQUAL(1, stalled_core_id);
esp_ipc_isr_release_other_cpu();
xSemaphoreTake(other_cpu_task_finished, portMAX_DELAY);
printf("Test end\n");
vSemaphoreDelete(other_cpu_task_finished);
}
TEST_CASE("Test nested stall other CPU release", "[ipc]")
{
printf("Test start\n");
esp_ipc_isr_stall_other_cpu();
TEST_ASSERT_TRUE(esp_ipc_isr_is_other_cpu_stalled());
esp_ipc_isr_stall_other_cpu();
TEST_ASSERT_TRUE(esp_ipc_isr_is_other_cpu_stalled());
esp_ipc_isr_release_other_cpu();
TEST_ASSERT_TRUE(esp_ipc_isr_is_other_cpu_stalled());
int stalled_core_id = -1;
esp_ipc_isr_call_blocking(esp_test_ipc_isr_get_other_core_id, &stalled_core_id);
TEST_ASSERT_EQUAL(1, stalled_core_id);
esp_ipc_isr_release_other_cpu();
TEST_ASSERT_FALSE(esp_ipc_isr_is_other_cpu_stalled());
printf("Test end\n");
}
TEST_CASE("Test stall other CPU safe with callbacks", "[ipc]")
{
SemaphoreHandle_t other_cpu_task_finished = xSemaphoreCreateBinary();
printf("Test start\n");
s_cmd_other_cpu_exit_critical_section = false;
s_other_cpu_in_critical_section = false;
xTaskCreatePinnedToCore(other_cpu_task_task_critical, "other_cpu_task", 4096, &other_cpu_task_finished, UNITY_FREERTOS_PRIORITY - 1, NULL, 1);
while (!s_other_cpu_in_critical_section) {
vTaskDelay(1);
}
TEST_ASSERT_EQUAL(ESP_ERR_NOT_ALLOWED, esp_ipc_isr_stall_other_cpu_safe());
s_cmd_other_cpu_exit_critical_section = true;
while (esp_ipc_isr_stall_other_cpu_safe() != ESP_OK) {
vTaskDelay(1);
}
TEST_ASSERT_TRUE(esp_ipc_isr_is_other_cpu_stalled());
int original_val = 0x12345678;
int expected_val = 0xa5a5;
int val = original_val;
esp_ipc_isr_call(esp_test_ipc_isr_callback, &val);
while (val != expected_val) {}
val = original_val;
esp_ipc_isr_call_blocking(esp_test_ipc_isr_callback, &val);
TEST_ASSERT_EQUAL_HEX(expected_val, val);
esp_ipc_isr_release_other_cpu();
xSemaphoreTake(other_cpu_task_finished, portMAX_DELAY);
printf("Test end\n");
vSemaphoreDelete(other_cpu_task_finished);
}
#endif /* CONFIG_ESP_IPC_ISR_ENABLE */

View File

@@ -97,6 +97,11 @@ The IPC feature offers the API listed below to execute a callback in a High Prio
- :cpp:func:`esp_ipc_isr_call` triggers an IPC call on the target core. This function will busy-wait until the target core **begins** execution of the callback.
- :cpp:func:`esp_ipc_isr_call_blocking` triggers an IPC call on the target core. This function will busy-wait until the target core **completes** execution of the callback.
These functions interrupt the other CPU and execute the callback in the context of a High Priority Interrupt. There are two common usage patterns:
- For simple callbacks that do not enter critical sections shared with the other CPU, call :cpp:func:`esp_ipc_isr_call` or :cpp:func:`esp_ipc_isr_call_blocking` directly.
- If the calling CPU may enter critical sections used by the other CPU, or if several callbacks must run while the other CPU remains stopped, first stall the other CPU using :cpp:func:`esp_ipc_isr_stall_other_cpu` or :cpp:func:`esp_ipc_isr_stall_other_cpu_safe`. Then use :cpp:func:`esp_ipc_isr_call` or :cpp:func:`esp_ipc_isr_call_blocking` to execute callbacks. After the operation is complete, release the other CPU with :cpp:func:`esp_ipc_isr_release_other_cpu`.
.. only:: CONFIG_IDF_TARGET_ARCH_XTENSA
The following code-blocks demonstrates a High Priority Interrupt IPC callback written in assembly that simply reads the target core's cycle count:
@@ -117,10 +122,23 @@ The IPC feature offers the API listed below to execute a callback in a High Prio
s32i a3, a2, 0
ret
The callback can be called directly when no shared critical section can deadlock:
.. code-block:: c
unit32_t cycle_count;
esp_ipc_isr_call_blocking(esp_test_ipc_isr_get_cycle_count_other_cpu, (void *)cycle_count);
uint32_t cycle_count;
esp_ipc_isr_call_blocking(esp_test_ipc_isr_get_cycle_count_other_cpu, (void *)&cycle_count);
Alternatively, safely stall the other CPU before making one or more IPC calls:
.. code-block:: c
while (esp_ipc_isr_stall_other_cpu_safe() != ESP_OK) {
// Optionally, add a timeout or yield to avoid infinite loop
}
uint32_t cycle_count;
esp_ipc_isr_call_blocking(esp_test_ipc_isr_get_cycle_count_other_cpu, (void *)&cycle_count);
esp_ipc_isr_release_other_cpu();
.. note::
@@ -144,6 +162,7 @@ The High Priority Interrupt IPC API also provides the following convenience func
:CONFIG_IDF_TARGET_ARCH_RISCV: - :cpp:func:`esp_ipc_isr_stall_other_cpu` stalls the target core. The calling core disables interrupts of level 3 and lower, while the target core will busy-wait with all interrupts disabled. The target core will busy-wait until :cpp:func:`esp_ipc_isr_release_other_cpu` is called.
:CONFIG_IDF_TARGET_ARCH_XTENSA: - :cpp:func:`esp_ipc_isr_stall_other_cpu` stalls the target core. The calling core disables interrupts of level 3 and lower while the target core will busy-wait with interrupts of level 5 and lower disabled. The target core will busy-wait until :cpp:func:`esp_ipc_isr_release_other_cpu` is called.
- :cpp:func:`esp_ipc_isr_stall_other_cpu_safe` attempts to stall the other core only if it is not in a critical section or ISR context. If the other core is in such a state, the function considers it unsafe to stall, releases the core, and returns an error.
- :cpp:func:`esp_ipc_isr_release_other_cpu` resumes the target core.
Application Examples

View File

@@ -97,6 +97,11 @@ IPC 功能提供了下列 API以在高优先级中断的上下文中执行回
- :cpp:func:`esp_ipc_isr_call` 能够在目标内核上触发一个 IPC 调用。在目标内核 **开始** 执行回调前,此函数将一直处于忙等待。
- :cpp:func:`esp_ipc_isr_call_blocking` 能够在目标内核上触发一个 IPC 调用。在目标内核 **完成** 回调执行前,此函数将一直处于忙等待。
这些函数会中断另一 CPU并在高优先级中断的上下文中执行回调。常见用法有两种
- 对于不会进入与另一 CPU 共享的临界区的简单回调,可以直接调用 :cpp:func:`esp_ipc_isr_call`:cpp:func:`esp_ipc_isr_call_blocking`
- 如果调用 CPU 可能进入另一 CPU 使用的临界区,或者需要在另一 CPU 保持停止时执行多个回调,则应先使用 :cpp:func:`esp_ipc_isr_stall_other_cpu`:cpp:func:`esp_ipc_isr_stall_other_cpu_safe` 暂停另一 CPU。然后使用 :cpp:func:`esp_ipc_isr_call`:cpp:func:`esp_ipc_isr_call_blocking` 执行回调。操作完成后,使用 :cpp:func:`esp_ipc_isr_release_other_cpu` 释放另一 CPU。
.. only:: CONFIG_IDF_TARGET_ARCH_XTENSA
以下示例代码用汇编语言编写了一个高优先级中断 IPC 回调,该回调的作用为读取目标内核的周期计数:
@@ -117,10 +122,23 @@ IPC 功能提供了下列 API以在高优先级中断的上下文中执行回
s32i a3, a2, 0
ret
如果不会因为共享临界区而发生死锁,可以直接调用该回调:
.. code-block:: c
unit32_t cycle_count;
esp_ipc_isr_call_blocking(esp_test_ipc_isr_get_cycle_count_other_cpu, (void *)cycle_count);
uint32_t cycle_count;
esp_ipc_isr_call_blocking(esp_test_ipc_isr_get_cycle_count_other_cpu, (void *)&cycle_count);
或者,也可以在进行一次或多次 IPC 调用之前,安全地暂停另一 CPU
.. code-block:: c
while (esp_ipc_isr_stall_other_cpu_safe() != ESP_OK) {
// 在生产代码中,可按需添加超时或 yield以避免无限循环。
}
uint32_t cycle_count;
esp_ipc_isr_call_blocking(esp_test_ipc_isr_get_cycle_count_other_cpu, (void *)&cycle_count);
esp_ipc_isr_release_other_cpu();
.. note::
@@ -144,6 +162,7 @@ IPC 功能提供了下列 API以在高优先级中断的上下文中执行回
:CONFIG_IDF_TARGET_ARCH_RISCV: - :cpp:func:`esp_ipc_isr_stall_other_cpu`:暂停目标内核。调用内核禁用 3 级及以下级别的中断,而目标内核将在所有中断被禁用的情况下进入忙等待。在调用 :cpp:func:`esp_ipc_isr_release_other_cpu` 前,目标内核会保持忙等待。
:CONFIG_IDF_TARGET_ARCH_XTENSA: - :cpp:func:`esp_ipc_isr_stall_other_cpu`:暂停目标内核。调用内核禁用 3 级及以下级别的中断,而目标内核将在 5 级及以下的中断被禁用的情况下进入忙等待。在调用 :cpp:func:`esp_ipc_isr_release_other_cpu` 前,目标内核会保持忙等待。
- :cpp:func:`esp_ipc_isr_stall_other_cpu_safe`:仅当另一内核不在临界区或 ISR 上下文中时,才尝试暂停该内核。如果另一内核处于此类状态,则认为暂停不安全,会释放该内核并返回错误。
- :cpp:func:`esp_ipc_isr_release_other_cpu`:恢复目标内核。
应用示例