esp_ipc: Update documentation and API descriptions

This commit updates the documentation and API descriptions of
the esp_ipc and esp_ipc_isr features.
This commit is contained in:
KonstantinKondrashov
2021-10-26 21:12:26 +08:00
committed by Darian Leung
parent 460f3ad7b6
commit 209702d055
12 changed files with 182 additions and 144 deletions

View File

@@ -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 </api-guides/hlinterrupts>` 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