From 209702d0550dc89d77fe2f1e7db67d43fd08321a Mon Sep 17 00:00:00 2001 From: KonstantinKondrashov Date: Tue, 26 Oct 2021 21:12:26 +0800 Subject: [PATCH] esp_ipc: Update documentation and API descriptions This commit updates the documentation and API descriptions of the esp_ipc and esp_ipc_isr features. --- components/esp_system/Kconfig | 28 ++--- components/esp_system/esp_ipc.c | 4 +- components/esp_system/include/esp_ipc.h | 54 ++++---- components/esp_system/include/esp_ipc_isr.h | 93 ++++++++------ .../esp_system/port/arch/xtensa/esp_ipc_isr.c | 1 + .../port/arch/xtensa/esp_ipc_isr_routines.S | 2 +- components/esp_system/test/test_ipc_isr.S | 2 +- docs/doxygen/Doxyfile_esp32 | 4 +- docs/doxygen/Doxyfile_esp32s3 | 4 +- docs/en/api-reference/system/ipc.rst | 116 +++++++++++------- examples/system/ipc/ipc_isr/README.md | 14 ++- examples/system/ipc/ipc_isr/main/asm_funcs.S | 4 +- 12 files changed, 182 insertions(+), 144 deletions(-) diff --git a/components/esp_system/Kconfig b/components/esp_system/Kconfig index a05bcfdc6d..af1c95131c 100644 --- a/components/esp_system/Kconfig +++ b/components/esp_system/Kconfig @@ -512,33 +512,27 @@ menu "IPC (Inter-Processor Call)" default 2048 if APPTRACE_ENABLE default 1024 help - Configure the IPC tasks stack size. One IPC task runs on each core - (in dual core mode), and allows for cross-core function calls. - - See IPC documentation for more details. - - The default stack size should be enough for most common use cases. - It can be shrunk if you are sure that you do not use any custom - IPC functionality. + Configure the IPC tasks stack size. An IPC task runs on each core (in dual core mode), and allows for + cross-core function calls. See IPC documentation for more details. The default IPC stack size should be + enough for most common simple use cases. However, users can increase/decrease the stack size to their + needs. config ESP_IPC_USES_CALLERS_PRIORITY bool "IPC runs at caller's priority" default y depends on !FREERTOS_UNICORE help - If this option is not enabled then the IPC task will keep behavior - same as prior to that of ESP-IDF v4.0, and hence IPC task will run - at (configMAX_PRIORITIES - 1) priority. + If this option is not enabled then the IPC task will keep behavior same as prior to that of ESP-IDF v4.0, + hence IPC task will run at (configMAX_PRIORITIES - 1) priority. config ESP_IPC_ISR_ENABLE bool default y if !FREERTOS_UNICORE help - This feature serves a similar purpose to the IPC except that the callback function is run - in the context of a level 4 interrupt (i.e., high priority/level interrupt). The IPC ISR - feature is intended for low latency execution of simple functions written in assembly on - another CPU. Due to being run in higher level interrupt context, the assembly functions - should be written in a particular way (see esp_test_ipc_isr_asm() and the "High-Level Interrupts" - chapter in hlinterrupts.rst for more details). + The IPC ISR feature is similar to the IPC feature except that the callback function is executed in the + context of a High Priority Interrupt. The IPC ISR feature is itended for low latency execution of simple + callbacks written in assembly on another CPU. Due to being run in a High Priority Interrupt, the assembly + callbacks must be written with particular restrictions (see "IPC" and "High-Level Interrupt" docs for more + details). endmenu # "IPC (Inter-Processor Call) diff --git a/components/esp_system/esp_ipc.c b/components/esp_system/esp_ipc.c index 64c7998fef..c68c36303f 100644 --- a/components/esp_system/esp_ipc.c +++ b/components/esp_system/esp_ipc.c @@ -195,6 +195,6 @@ esp_err_t esp_ipc_start_gcov_from_isr(uint32_t cpu_id, esp_ipc_func_t func, void return ret == pdTRUE ? ESP_OK : ESP_FAIL; } -#endif +#endif // CONFIG_APPTRACE_GCOV_ENABLE -#endif // not CONFIG_FREERTOS_UNICORE or CONFIG_APPTRACE_GCOV_ENABLE +#endif // !defined(CONFIG_FREERTOS_UNICORE) || defined(CONFIG_APPTRACE_GCOV_ENABLE) diff --git a/components/esp_system/include/esp_ipc.h b/components/esp_system/include/esp_ipc.h index 6f3894b07d..3be0620d00 100644 --- a/components/esp_system/include/esp_ipc.h +++ b/components/esp_system/include/esp_ipc.h @@ -4,8 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -#ifndef __ESP_IPC_H__ -#define __ESP_IPC_H__ +#pragma once #include @@ -15,30 +14,30 @@ extern "C" { #if !defined(CONFIG_FREERTOS_UNICORE) || defined(CONFIG_APPTRACE_GCOV_ENABLE) -/** @cond */ -typedef void (*esp_ipc_func_t)(void* arg); -/** @endcond */ /* * Inter-processor call APIs * - * FreeRTOS provides several APIs which can be used to communicate between - * different tasks, including tasks running on different CPUs. - * This module provides additional APIs to run some code on the other CPU. - * - * These APIs can only be used when FreeRTOS scheduler is running. + * FreeRTOS provides several APIs which can be used to communicate between different tasks, including tasks running on + * different CPUs. This module provides additional APIs to run some code on the other CPU. These APIs can only be used + * when FreeRTOS scheduler is running. */ /** - * @brief Execute a function on the given CPU + * @brief IPC Callback * - * Run a given function on a particular CPU. The given function must accept a - * void* argument and return void. The given function is run in the context of - * the IPC task of the CPU specified by the cpu_id parameter. The calling task - * will be blocked until the IPC task begins executing the given function. If - * another IPC call is ongoing, the calling task will block until the other IPC - * call completes. The stack size allocated for the IPC task can be configured - * in the "Inter-Processor Call (IPC) task stack size" setting in menuconfig. - * Increase this setting if the given function requires more stack than default. + * A callback of this type should be provided as an argument when calling esp_ipc_call() or esp_ipc_call_blocking(). + */ +typedef void (*esp_ipc_func_t)(void* arg); + +/** + * @brief Execute a callback on a given CPU + * + * Execute a given callback on a particular CPU. The callback must be of type "esp_ipc_func_t" and will be invoked in + * the context of the target CPU's IPC task. + * + * - This function will block the target CPU's IPC task has begun execution of the callback + * - If another IPC call is ongoing, this function will block until the ongoing IPC call completes + * - The stack size of the IPC task can be configured via the CONFIG_ESP_IPC_TASK_STACK_SIZE option * * @note In single-core mode, returns ESP_ERR_INVALID_ARG for cpu_id 1. * @@ -55,17 +54,10 @@ esp_err_t esp_ipc_call(uint32_t cpu_id, esp_ipc_func_t func, void* arg); /** - * @brief Execute a function on the given CPU and blocks until it completes + * @brief Execute a callback on a given CPU until and block until it completes * - * Run a given function on a particular CPU. The given function must accept a - * void* argument and return void. The given function is run in the context of - * the IPC task of the CPU specified by the cpu_id parameter. The calling task - * will be blocked until the IPC task completes execution of the given function. - * If another IPC call is ongoing, the calling task will block until the other - * IPC call completes. The stack size allocated for the IPC task can be - * configured in the "Inter-Processor Call (IPC) task stack size" setting in - * menuconfig. Increase this setting if the given function requires more stack - * than default. + * This function is identical to esp_ipc_call() except that this function will block until the execution of the callback + * completes. * * @note In single-core mode, returns ESP_ERR_INVALID_ARG for cpu_id 1. * @@ -80,10 +72,8 @@ esp_err_t esp_ipc_call(uint32_t cpu_id, esp_ipc_func_t func, void* arg); */ esp_err_t esp_ipc_call_blocking(uint32_t cpu_id, esp_ipc_func_t func, void* arg); -#endif // not CONFIG_FREERTOS_UNICORE or CONFIG_APPTRACE_GCOV_ENABLE +#endif // !defined(CONFIG_FREERTOS_UNICORE) || defined(CONFIG_APPTRACE_GCOV_ENABLE) #ifdef __cplusplus } #endif - -#endif /* __ESP_IPC_H__ */ diff --git a/components/esp_system/include/esp_ipc_isr.h b/components/esp_system/include/esp_ipc_isr.h index 7b6e060f1d..2934578951 100644 --- a/components/esp_system/include/esp_ipc_isr.h +++ b/components/esp_system/include/esp_ipc_isr.h @@ -14,31 +14,38 @@ extern "C" { #ifdef CONFIG_ESP_IPC_ISR_ENABLE -/** @cond */ +/** + * @brief IPC ISR Callback + * + * A callback of this type should be provided as an argument when calling esp_ipc_isr_asm_call() or + * esp_ipc_isr_asm_call_blocking(). + */ typedef void (*esp_ipc_isr_func_t)(void* arg); -/** @endcond */ /** - * @brief Initialize inter-processor call module which based on #4 high-interrupt. + * @brief Initialize the IPC ISR feature * - * This function is called on CPU start and should not be called from the application. + * This function initializes the IPC ISR feature and must be called before any other esp_ipc_isr...() functions. + * The IPC ISR feature allows for callbacks (written in assembly) to be run on a particular CPU in the context of a + * High Priority Interrupt. * - * This function starts two tasks, one on each CPU. These tasks register - * #4 High-interrupt and after that, the tasks are deleted. - * The next API functions work with this functionality: - * esp_ipc_isr_asm_call - * esp_ipc_isr_asm_call_blocking - * They allow to run an asm function on other CPU. + * - This function will register a High Priority Interrupt on each CPU. The priority of the interrupts is dependent on + * the CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL option. + * - Callbacks written in assembly can then run in context of the registered High Priority Interrupts + * - Callbacks can be executed by calling esp_ipc_isr_asm_call() or esp_ipc_isr_asm_call_blocking() */ void esp_ipc_isr_init(void); /** - * @brief Execute an asm function on the other CPU (uses the #4 high-priority interrupt) + * @brief Execute an assembly callback on the other CPU * - * @note In single-core mode, it is not available. - * This function calls the #4 high-priority interrupt on the other CPU. - * The given function is called in the context of the interrupt by CALLX0 command and - * operates with registers a2, a3, a4. + * Execute a given callback on the other CPU in the context of a High Priority Interrupt. + * + * - This function will busy-wait in a critical section until the other CPU has started execution of the callback + * - The callback must be written in assembly, is invoked using a CALLX0 instruction, and has a2, a3, a4 as scratch + * registers. See docs for more details + * + * @note This function is not available in single-core mode. * * @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 @@ -46,11 +53,12 @@ void esp_ipc_isr_init(void); void esp_ipc_isr_asm_call(esp_ipc_isr_func_t func, void* arg); /** - * @brief Execute an asm function on the other CPU and blocks until it completes (uses the #4 high-priority interrupt) + * @brief Execute an assembly callback on the other CPU and busy-wait until it completes * - * @note In single-core mode, it is not available. - * This function calls the #4 high-priority interrupt on the other CPU. - * The given function is called in the context of the interrupt by CALLX0 command. + * This function is identical to esp_ipc_isr_asm_call() except that this function will busy-wait until the execution of + * the callback completes. + * + * @note This function is not available in single-core mode. * * @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 @@ -58,51 +66,60 @@ void esp_ipc_isr_asm_call(esp_ipc_isr_func_t func, void* arg); void esp_ipc_isr_asm_call_blocking(esp_ipc_isr_func_t func, void* arg); /** - * @brief Stall the other CPU and the current CPU disables interrupts with level 3 and lower. + * @brief Stall the other CPU * - * @note In single-core mode, it is not available. - * This function calls the #4 high-priority interrupt on the other CPU. - * The esp_ipc_isr_finish_cmd() function is called on the other CPU in the context of the #4 high-priority interrupt. - * The esp_ipc_isr_finish_cmd is called by CALLX0 command. - * It is waiting for the end command. The command will be sent by esp_ipc_isr_release_other_cpu(). - * This function is used for DPORT workaround. + * 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 blocks other CPU until the release call esp_ipc_isr_release_other_cpu(). + * - 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 * - * This fucntion is used for the DPORT workaround: stall other cpu that this cpu is pending to access dport register start. + * @note This function is not available in single-core mode. */ void esp_ipc_isr_stall_other_cpu(void); /** * @brief Release the other CPU * - * @note In single-core mode, it is not available. - * This function will send the end command to release the stall other CPU. - * This function is used for DPORT workaround: stall other cpu that this cpu is pending to access dport register end. + * This function will release the other CPU that was previously stalled from calling esp_ipc_isr_stall_other_cpu() * + * - 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 + * + * @note This function is not available in single-core mode. */ void esp_ipc_isr_release_other_cpu(void); /** - * @brief Pause stall the other CPU + * @brief Puase the CPU stall feature + * + * This function will pause the CPU stall feature. Once paused, calls to esp_ipc_isr_stall_other_cpu() and + * esp_ipc_isr_release_other_cpu() will have no effect. If a IPC ISR call is already in progress, this function will + * busy-wait until the call completes before pausing the CPU stall feature. */ void esp_ipc_isr_stall_pause(void); /** - * @brief Abort stall the other CPU + * @brief Abort a CPU stall * - * This routine does not stop the stall routines in any way that is recoverable. - * Please only call in case of panic(). - * Used in panic code: the enter_critical stuff may be messed up so we just stop everything without checking the mux. + * This function will abort any stalling routine of the other CPU due to a pervious 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(). + * + * - This function is used in panic handling code */ void esp_ipc_isr_stall_abort(void); /** - * @brief Resume stall the other CPU + * @brief Resume the CPU stall feature + * + * This function will resume the CPU stall feature that was previously paused by calling esp_ipc_isr_stall_pause(). Once + * resumed, calls to esp_ipc_isr_stall_other_cpu() and esp_ipc_isr_release_other_cpu() will have effect again. */ void esp_ipc_isr_stall_resume(void); -#else // not CONFIG_ESP_IPC_ISR_ENABLE +#else // CONFIG_ESP_IPC_ISR_ENABLE #define esp_ipc_isr_stall_other_cpu() #define esp_ipc_isr_release_other_cpu() diff --git a/components/esp_system/port/arch/xtensa/esp_ipc_isr.c b/components/esp_system/port/arch/xtensa/esp_ipc_isr.c index 156079b71a..fbd5e589ea 100644 --- a/components/esp_system/port/arch/xtensa/esp_ipc_isr.c +++ b/components/esp_system/port/arch/xtensa/esp_ipc_isr.c @@ -160,6 +160,7 @@ void IRAM_ATTR esp_ipc_isr_stall_pause(void) void IRAM_ATTR esp_ipc_isr_stall_abort(void) { + //Note: We don't enter a critical section here as we are calling this from a panic. s_stall_state = STALL_STATE_IDLE; } diff --git a/components/esp_system/port/arch/xtensa/esp_ipc_isr_routines.S b/components/esp_system/port/arch/xtensa/esp_ipc_isr_routines.S index 77b6b0406b..01ddf8b528 100644 --- a/components/esp_system/port/arch/xtensa/esp_ipc_isr_routines.S +++ b/components/esp_system/port/arch/xtensa/esp_ipc_isr_routines.S @@ -11,7 +11,7 @@ /* esp_ipc_isr_waiting_for_finish_cmd(void* finish_cmd) * - * It should be called by the CALLX0 command from the handler of High-priority interrupt (4 lvl). + * 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. */ .section .iram1, "ax" diff --git a/components/esp_system/test/test_ipc_isr.S b/components/esp_system/test/test_ipc_isr.S index a448e38997..064443f3c1 100644 --- a/components/esp_system/test/test_ipc_isr.S +++ b/components/esp_system/test/test_ipc_isr.S @@ -11,7 +11,7 @@ /* esp_test_ipc_isr_asm(void *arg) * - * It should be called by the CALLX0 command from the handler of High-priority interrupt (4 lvl). + * 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. */ .section .iram1, "ax" diff --git a/docs/doxygen/Doxyfile_esp32 b/docs/doxygen/Doxyfile_esp32 index 4f6ef7e2e8..1b6c20ca19 100644 --- a/docs/doxygen/Doxyfile_esp32 +++ b/docs/doxygen/Doxyfile_esp32 @@ -9,4 +9,6 @@ INPUT += \ $(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/touch_sensor_channel.h \ $(PROJECT_PATH)/components/driver/$(IDF_TARGET)/include/driver/touch_sensor.h \ $(PROJECT_PATH)/components/esp_hw_support/include/soc/esp32/himem.h \ - $(PROJECT_PATH)/components/ulp/include/$(IDF_TARGET)/ulp.h + $(PROJECT_PATH)/components/ulp/include/$(IDF_TARGET)/ulp.h \ + $(PROJECT_PATH)/components/esp_system/include/esp_ipc.h \ + $(PROJECT_PATH)/components/esp_system/include/esp_ipc_isr.h diff --git a/docs/doxygen/Doxyfile_esp32s3 b/docs/doxygen/Doxyfile_esp32s3 index 1f081a8981..f3bd42dc1b 100644 --- a/docs/doxygen/Doxyfile_esp32s3 +++ b/docs/doxygen/Doxyfile_esp32s3 @@ -11,4 +11,6 @@ INPUT += \ $(PROJECT_PATH)/components/usb/include/usb/usb_helpers.h \ $(PROJECT_PATH)/components/usb/include/usb/usb_host.h \ $(PROJECT_PATH)/components/usb/include/usb/usb_types_ch9.h \ - $(PROJECT_PATH)/components/usb/include/usb/usb_types_stack.h + $(PROJECT_PATH)/components/usb/include/usb/usb_types_stack.h \ + $(PROJECT_PATH)/components/esp_system/include/esp_ipc.h \ + $(PROJECT_PATH)/components/esp_system/include/esp_ipc_isr.h diff --git a/docs/en/api-reference/system/ipc.rst b/docs/en/api-reference/system/ipc.rst index 0aa9fd0541..932c6dd768 100644 --- a/docs/en/api-reference/system/ipc.rst +++ b/docs/en/api-reference/system/ipc.rst @@ -1,71 +1,84 @@ Inter-Processor Call ==================== +.. note:: + The IPC is an **Inter-Processor Call** and **NOT Inter-Process Communication** as found on other operating systems. + Overview -------- -Due to the dual core nature of the {IDF_TARGET_NAME}, there are instances where a certain -function must be run in the context of a particular core (e.g. allocating -ISR to an interrupt source of a particular core). The IPC (Inter-Processor -Call) feature allows for the execution of functions on a particular CPU. +Due to the dual core nature of the {IDF_TARGET_NAME}, there are instances where a certain callback must be run in the context of a particular CPU such as: -A given function can be executed on a particular core by calling -:cpp:func:`esp_ipc_call` or :cpp:func:`esp_ipc_call_blocking`. IPC is -implemented via two high priority FreeRTOS tasks pinned to each CPU known as -the IPC Tasks. The two IPC Tasks remain inactive (blocked) until -:cpp:func:`esp_ipc_call` or :cpp:func:`esp_ipc_call_blocking` is called. When -an IPC Task of a particular core is unblocked, it will preempt the current -running task on that core and execute a given function. +- When allocating an ISR to an interrupt source of a particular CPU (applies to freeing a particular CPU's interrupt source as well). +- On particular chips (such as the ESP32), accessing memory that is exclusive to a particular CPU (such as RTC Fast Memory). +- Reading the registers/state of another CPU. -Usage ------ +The IPC (Inter-Processor Call) feature allows a particular CPU (the calling CPU) to trigger the execution of a callback function on another CPU (the target CPU). The IPC feature allows execution of a callback function on the target CPU in either a task context, or a High Priority Interrupt context (see :doc:`/api-guides/hlinterrupts` for more details). Depending on the context that the callback function is executed in, different restrictions apply to the implementation of the callback function. -:cpp:func:`esp_ipc_call` unblocks the IPC task on a particular core to execute -a given function. The task that calls :cpp:func:`esp_ipc_call` will be blocked -until the IPC Task begins execution of the given function. -:cpp:func:`esp_ipc_call_blocking` is similar but will block the calling task -until the IPC Task has completed execution of the given function. +IPC in Task Context +------------------- -Functions executed by IPCs must be functions of type -`void func(void *arg)`. To run more complex functions which require a larger -stack, the IPC tasks' stack size can be configured by modifying -:ref:`CONFIG_ESP_IPC_TASK_STACK_SIZE` in `menuconfig`. The IPC API is protected by a -mutex hence simultaneous IPC calls are not possible. +The IPC feature implements callback execution in a task context by creating an IPC task for each CPU during application startup. When the calling CPU needs to execute a callback on the target CPU, the callback will execute in the context of the target CPU's IPC task. -Care should taken to avoid deadlock when writing functions to be executed by -IPC, especially when attempting to take a mutex within the function. +When using IPCs in a task context, users need to consider the following: -.. _hi_priority_ipc: +- IPC callbacks should ideally be simple and short. **An IPC callback should avoid attempting to block or yield**. +- The IPC tasks are created at the highest possible priority (i.e., ``configMAX_PRIORITIES - 1``) thus the callback should also run at that priority as a result. However, :ref:`CONFIG_ESP_IPC_USES_CALLERS_PRIORITY` is enabled by default which will temporarily lower the priority of the target CPU's IPC task to the calling CPU before executing the callback. +- Depending on the complexity of the callback, users may need to configure the stack size of the IPC task via :ref:`CONFIG_ESP_IPC_TASK_STACK_SIZE`. +- The IPC feature is internally protected by a mutex. Therefore, simultaneous IPC calls from two or more calling CPUs will be handled on a first come first serve basis. -Hi-priority IPC ---------------- +API Usage +^^^^^^^^^ -In some cases, we need to quickly get the state of the other CPU (for example, in the core dump, in the GDB stub, in some unit tests, in the dport workaround, etc.), and usually it can be implemented as a fairly simple function and can be written in assembler. -For these purposes, the IPC API has special functions :cpp:func:`esp_ipc_isr_asm_call` and :cpp:func:`esp_ipc_isr_asm_call_blocking`. -The `esp_ipc_isr_asm...(asm_func, arg)` functions trigger the High-priority interrupt (4 level) on the other CPU. The given assembler function (asm_func) will run on the other CPU in the context of the interrupt. This assembler function will be called by the CALLX0 command therefore it must be written in assembler. +Task Context IPC callbacks have the following restrictions: -:cpp:func:`esp_ipc_isr_asm_call` - triggers the Hi-priority interrupt on a particular core to execute a given function. The CPU which called this function will be blocked until the other CPU begins execution of the given function. +- The callback must be of type ``void func(void *arg)`` +- The callback should avoid attempting to block or yield as this will result in the target CPU's IPC task blocking or yielding. +- The callback must avoid changing any aspect of the IPC task (e.g., by calling ``vTaskPrioritySet(NULL, x)``). -:cpp:func:`esp_ipc_isr_asm_call_blocking` is similar but will block the calling CPU until the other CPU has completed the execution of the given function. +The IPC feature offers the API listed below to execute a callback in a task context on a target CPU. The API allows the calling CPU to block until the callback's execution has completed, or return immediately once the callback's execution has started. -:cpp:func:`esp_ipc_isr_stall_other_cpu` stalls the other CPU and the calling CPU disables interrupts with level 3 and lower. To finish stalling the other CPU call :cpp:func:`esp_ipc_isr_release_other_cpu`. The stalled CPU disables interrupts with level 5 and lower. +- :cpp:func:`esp_ipc_call` will trigger an IPC call on the target CPU. This function will block until the target CPU's IPC task **begins** execution of the callback. +- :cpp:func:`esp_ipc_call_blocking` will trigger an IPC on the target CPU. This function will block until the target CPU's IPC task **completes** execution of the callback. -Functions executed by Hi-priority IPC must be functions of type `void func(void *arg)`. Examples of a assembler function see in :idf_file:`components/esp_system/port/arch/xtensa/esp_ipc_isr_routines.S`, :idf_file:`components/esp_system/test/test_ipc_isr.S` and below. In the asm function, you can use only a few registers as they were saved in the interrupt handler before calling this function, their use is safe. The registers:`a2` as `void *arg`, a3 and a4 are free for use. +IPC in ISR Context +------------------ -Some feature: +In some cases, we need to quickly obtain the state of another CPU such as in a core dump, GDB stub, various unit tests, and DPORT workaround. For such scenarios, the IPC feature supports execution of callbacks in a :doc:`High Priority Interrupt ` context. The IPC feature implements the High Priority Interrupt context by reserving a High Priority Interrupt on each CPU for IPC usage. When a calling CPU needs to execute a callback on the target CPU, the callback will execute in the context of the High Priority Interrupt of the target CPU. -- The asm function should be placed in the IRAM memory and aligned on a memory address multiple of 4. -- As the asm function is run in the context of the High-priority interrupt, a C function can no be called because the windows spill is disabled. -- Use only a2, a3 and a4 registers in the asm function (to workaround it see esp_complex_asm_func). -- A CPU, that called these APIs `esp_ipc_isr_asm...(asm_func, arg)`, disables interrupts with level 3 and lower. -- A CPU, where the asm function is executed, disables interrupts with level 5 and lower. -- You do not need to take care about handling of the Hi-priority interrupt. +When using IPCs in High Priority Interrupt context, users need to consider the following: +- Since the callback is executed in a High Priority Interrupt context, the callback must be written entirely in assembly. See the API Usage below for more details regarding writing assembly callbacks. +- The priority of the reserved High Priority Interrupt is dependent on the :ref:`CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL` option +- When the callback executes: + - The calling CPU will disable interrupts of level 3 and lower + - Although the priority of the reserved interrupt depends on :ref:`CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL`, during the execution IPC ISR callback, the target CPU will disable interrupts of level 5 and lower regardless of what :ref:`CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL` is set to. + +API Usage +^^^^^^^^^ + +High Priority Interrupt IPC callbacks have the following restrictions: + +- The callback must be of type ``void func(void *arg)`` but implemented entirely in assembly +- The callback is invoked via the ``CALLX0`` instruction with register windowing disabled, thus the callback: + - Must not call any register window related instructions (e.g., ``entry`` and ``retw``). + - Must not call other C functions as register windowing is disabled +- The callback should be placed in IRAM at a 4-byte aligned address +- (On invocation of/after returning from) the callback, the registers ``a2, a3, a4`` are (saved/restored) automatically thus can be used in the callback. The callback should **ONLY** use those registers. + - ``a2`` will contain the ``void *arg`` of the callback + - ``a3/a4`` are free to use as scratch registers + +The IPC feature offers the API listed below to execute a callback in a High Priority Interrupt context. + +- :cpp:func:`esp_ipc_isr_asm_call` will trigger an IPC call on the target CPU. This function will busy-wait until the target CPU begins execution of the callback. +- :cpp:func:`esp_ipc_isr_asm_call_blocking` will trigger an IPC call on the target CPU. This function will busy-wait until the target CPU completes execution of the callback. + +The following code-blocks demonstrates a High Priority Interrupt IPC callback written in assembly that simply reads the target CPU's cycle count. .. code-block:: asm /* esp_test_ipc_isr_get_cycle_count_other_cpu(void *arg) */ - // this function puts CCOUNT of the other CPU in the arg. + // this function reads CCOUNT of the target CPU and stores it in arg. // use only a2, a3 and a4 regs here. .section .iram1, "ax" .align 4 @@ -78,10 +91,25 @@ Some feature: s32i a3, a2, 0 ret +.. code-block:: c -This number of registers available for use is sufficient for most simple cases. But if you need to run a more complex asm function, you can pass as an argument a pointer to a structure that can accept additional registers to a buffer to make them free to use. Remember to restore them before returning See the :example:`system/ipc/ipc_isr`. + unit32_t cycle_count; + esp_ipc_isr_asm_call_blocking(esp_test_ipc_isr_get_cycle_count_other_cpu, (void *)cycle_count); + +.. note:: + The number of scratch registers available for use is sufficient for most simple use cases. But if your callback requires more scratch registers, ``void *arg`` can point to a buffer that is used as a register save area. The callback can then save and restore more registers. See the :example:`system/ipc/ipc_isr`. + +.. note:: + For more examples of High Priority Interrupt IPC callbacks, see :idf_file:`components/esp_system/port/arch/xtensa/esp_ipc_isr_routines.S` and :`components/esp_system/test/test_ipc_isr.S` + +The High Priority Interrupt IPC API also provides the following convenience functions that can stall/resume the target CPU. These API utilize the High Priority Interrupt IPC, but supply their own internal callbacks: + +- :cpp:func:`esp_ipc_isr_stall_other_cpu` stalls the target CPU. The calling CPU disables interrupts of level 3 and lower while the target CPU will busy-wait with interrupts of level 5 and lower disabled. The target CPU will busy-wait until :cpp:func:`esp_ipc_isr_release_other_cpu` is called. +- :cpp:func:`esp_ipc_isr_release_other_cpu` resumes the target CPU. API Reference ------------- .. include-build-file:: inc/esp_ipc.inc + +.. include-build-file:: inc/esp_ipc_isr.inc diff --git a/examples/system/ipc/ipc_isr/README.md b/examples/system/ipc/ipc_isr/README.md index 5a5dac893b..0ee9ee4eb8 100644 --- a/examples/system/ipc/ipc_isr/README.md +++ b/examples/system/ipc/ipc_isr/README.md @@ -1,14 +1,18 @@ | Supported Targets | ESP32 | ESP32-S3 | | ----------------- | ----- | -------- | -# Hi-priority IPC example +# IPC ISR Example -This example demonstrates how to use Hi-priority IPC, it is based on Hi-priority interrupt (level 4). This feature can be useful in case when need quickly to get the state of the other CPU (consult the [Hi-priority IPC](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/ipc.html#hi-priority-ipc)). -In the `asm_funcs.S` file are functions that will be run on the other core. The function should be fairly simple that can be written in assembler. +This example demonstrates how to use the IPC ISR feature (which allows an IPC to run in the context of a High Priority Interrupt). The level of the IPC ISR interrupt depends on the `CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL` option. The IPC ISR feature can be useful in cases where users need to quickly get the state of the other CPU (consult the [IPC documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/ipc.html)). The `asm_funcs.S` file contains the callback that will be run on the other core. The callback should be fairly simple and must be entirely in assembly. -The first asm function `get_ps_other_cpu()` demonstrates a simple way of usage, it returns the PS register of other core. +The first assembly callback `get_ps_other_cpu()` demonstrates a callback that simply returns the `PS` register of other core. -The second asm function `extended_ipc_isr_asm()` demonstrates way when need to do some complex operations on other core, for this, save registers to the buffer (regs[]) making them available for use. Then using passed arguments (in[]) do some work and write the result to out[]. At the end recover saved registers. +The second assembly callback `extended_ipc_isr_asm()` demonstrates a more complex callback that uses a buffer (provided as the callback's argument) to save some registers and return multiple values from the callback. The callback's `void *arg` points to a buffer containing the following: + - `uint32_t regs[];` that gives the callback an area to save some of the CPUs registers. Saving the registers gives the callback more scratch registers to use. + - `uint32_t in[];` that gives the callback multiple input arguments + - `uint32_t out[];` that gives the callback multiple output arguments + +The `extended_ipc_isr_asm()` callback will simply save/restore registers to/from `regs[]`, then use the arguments passed by `in[]` to do some work, then write the results to `out[]`. ## How to use example diff --git a/examples/system/ipc/ipc_isr/main/asm_funcs.S b/examples/system/ipc/ipc_isr/main/asm_funcs.S index 49c1b916cb..83e3a5e9ed 100644 --- a/examples/system/ipc/ipc_isr/main/asm_funcs.S +++ b/examples/system/ipc/ipc_isr/main/asm_funcs.S @@ -11,7 +11,7 @@ /* get_ps_other_cpu(void *arg) * - * It should be called by the CALLX0 command from the handler of High-priority interrupt (4 lvl). + * 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. * Returns PS. */ @@ -29,7 +29,7 @@ get_ps_other_cpu: /* extended_ipc_isr_asm(void *arg) * - * It should be called by the CALLX0 command from the handler of High-priority interrupt (4 lvl). + * 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. * This function receives a structure (arg) where can be saved some regs * to get them available here, at the end of the function we recover the saved regs.