# 处理器间调用 (IPC)

[English]

备注

IPC 指 处理器间调用 (Inter-Processor Call)，而不是其他操作系统中所指的 进程间通信 (Inter-Process Communication)。

## 概述

由于 ESP32-H4 的双核特性，在某些情况下，某个回调必须在特定的内核上下文中运行。例如：

- 为特定内核的中断源分配 ISR，或释放特定内核的中断源时

- 在特定芯片（如 ESP32）上访问某个内核独有的内存，如 RTC Fast Memory 时

- 读取另一个内核的寄存器或状态时

IPC 功能允许一个特定的内核（下文称“调用内核”）触发另一个内核（下文称“目标内核”）的回调函数执行，并允许目标内核在任务上下文或中断上下文中执行回调函数。在不同的上下文中，回调函数的实现限制有所不同。

## 任务上下文中的 IPC

在应用程序启动时，IPC 功能为每个内核创建一个 IPC 任务，从而在任务上下文中执行回调。当调用内核需要在目标内核中执行回调时，回调将在目标内核的 IPC 任务的上下文中执行。

在任务上下文中使用 IPC 功能时，需考虑以下几点：

- IPC 回调应该尽可能简短。 IPC 回调决不能阻塞或让出。

- IPC 任务是以尽可能高的优先级创建的（即 `configMAX_PRIORITIES - 1`）。

  > - 如果启用了 CONFIG_ESP_IPC_USES_CALLERS_PRIORITY，执行回调前会降低目标内核的 IPC 任务优先级，使其等于调用内核的优先级。

  > - 如果禁用了 CONFIG_ESP_IPC_USES_CALLERS_PRIORITY，目标内核将始终以尽可能高的优先级执行回调。

- 如果回调较为复杂，用户可能需要通过 CONFIG_ESP_IPC_TASK_STACK_SIZE 来配置 IPC 任务的堆栈大小。

- IPC 功能受内部互斥锁保护。因此，如果同时收到来自两个或多个调用内核的 IPC 请求，将按照“先到先得”的原则按顺序处理。

### API 用法

任务上下文中的 IPC 回调具有以下限制：

- 回调类型必须是 `esp_ipc_func_t`。

- 回调 决不能阻塞或让出，以免导致目标内核的 IPC 任务阻塞或让出。

- 回调必须避免改变 IPC 任务的任何状态，例如，不能调用 `vTaskPrioritySet(NULL, x)`。

IPC 功能提供了以下 API，用于在目标内核的任务上下文中执行回调。这两个 API 允许调用内核在回调执行完成前处于阻塞，或者在回调开始执行后立即返回。

- `esp_ipc_call()` 会在目标内核上触发一个 IPC 调用。在目标内核的 IPC 任务 开始 执行回调前，此函数会一直处于阻塞状态。

- `esp_ipc_call_blocking()` 会在目标内核上触发一个 IPC。在目标内核的 IPC 任务 完成 回调执行前，此函数会一直处于阻塞状态。

## 中断上下文中的 IPC

在某些情况下，我们需要快速获取另一个内核的状态，如在核心转储、GDB Stub、各种单元测试和绕过硬件错误的过程中。IPC ISR 功能会在每个内核上保留一个高优先级中断供 IPC 使用，从而在高优先级中断上下文中执行回调。当调用内核需要在目标内核上执行回调时，该回调将在目标内核的高优先级中断的上下文中执行。

在高优先级中断上下文中使用 IPC 时，需要考虑以下几点：

- 保留的高优先级中断的优先级取决于 CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL 选项。

当回调执行时，需考虑以下几点：

- 调用内核会禁用 3 级及以下优先级的中断。

- 虽然保留中断的优先级取决于 CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL，但是在执行 IPC ISR 回调期间，目标内核会禁用所有的中断。

### API 用法

高优先级中断 IPC 回调函数的类型必须是 `esp_ipc_isr_func_t`，其限制条件与常规中断处理程序相同。回调函数可以用 C 语言编写。

IPC 功能提供了下列 API，以在高优先级中断的上下文中执行回调：

- `esp_ipc_isr_call()` 能够在目标内核上触发一个 IPC 调用。在目标内核 开始 执行回调前，此函数将一直处于忙等待。

- `esp_ipc_isr_call_blocking()` 能够在目标内核上触发一个 IPC 调用。在目标内核 完成 回调执行前，此函数将一直处于忙等待。

这些函数会中断另一 CPU，并在高优先级中断的上下文中执行回调。常见用法有两种：

- 对于不会进入与另一 CPU 共享的临界区的简单回调，可以直接调用 `esp_ipc_isr_call()` 或 `esp_ipc_isr_call_blocking()`。

- 如果调用 CPU 可能进入另一 CPU 使用的临界区，或者需要在另一 CPU 保持停止时执行多个回调，则应先使用 `esp_ipc_isr_stall_other_cpu()` 或 `esp_ipc_isr_stall_other_cpu_safe()` 暂停另一 CPU。然后使用 `esp_ipc_isr_call()` 或 `esp_ipc_isr_call_blocking()` 执行回调。操作完成后，使用 `esp_ipc_isr_release_other_cpu()` 释放另一 CPU。

请前往 examples/system/ipc/ipc_isr/riscv/main/main.c 查看使用示例。

高优先级中断 IPC API 还提供了以下便利函数，这些函数可以暂停或恢复目标内核的执行。这些 API 利用高优先级中断 IPC，但同时提供了自己的内部回调函数：

- `esp_ipc_isr_stall_other_cpu()`：暂停目标内核。调用内核禁用 3 级及以下级别的中断，而目标内核将在所有中断被禁用的情况下进入忙等待。在调用 `esp_ipc_isr_release_other_cpu()` 前，目标内核会保持忙等待。

- `esp_ipc_isr_stall_other_cpu_safe()`：仅当另一内核不在临界区或 ISR 上下文中时，才尝试暂停该内核。如果另一内核处于此类状态，则认为暂停不安全，会释放该内核并返回错误。

- `esp_ipc_isr_release_other_cpu()`：恢复目标内核。

## 应用示例

- system/ipc/ipc_isr/riscv 演示了如何在 ESP32-H4 上使用 IPC ISR 功能在高优先级中断的上下文中运行 IPC，包括如何快速获取另一个 CPU 的状态以及如何从回调函数返回多个值。

## API 参考

### Header File

- components/esp_system/include/esp_ipc.h

- This header file can be included with:

  >     #include "esp_ipc.h"

### Functions

esp_err_t esp_ipc_call(uint32_t cpu_id, esp_ipc_func_t func, void *arg)

  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

  备注

  In single-core mode, returns ESP_ERR_INVALID_ARG for cpu_id 1.

  参数:

    - cpu_id -- [in] CPU where the given function should be executed (0 or 1)

    - func -- [in] Pointer to a function of type void func(void* arg) to be executed

    - arg -- [in] Arbitrary argument of type void* to be passed into the function

  返回:

    - ESP_ERR_INVALID_ARG if cpu_id is invalid

    - ESP_ERR_INVALID_STATE if the FreeRTOS scheduler is not running

    - ESP_OK otherwise

esp_err_t esp_ipc_call_blocking(uint32_t cpu_id, esp_ipc_func_t func, void *arg)

  Execute a callback on a given CPU until and block until it completes.

  This function is identical to esp_ipc_call() except that this function will block until the execution of the callback completes.

  备注

  In single-core mode, returns ESP_ERR_INVALID_ARG for cpu_id 1.

  参数:

    - cpu_id -- [in] CPU where the given function should be executed (0 or 1)

    - func -- [in] Pointer to a function of type void func(void* arg) to be executed

    - arg -- [in] Arbitrary argument of type void* to be passed into the function

  返回:

    - ESP_ERR_INVALID_ARG if cpu_id is invalid

    - ESP_ERR_INVALID_STATE if the FreeRTOS scheduler is not running

    - ESP_OK otherwise

### Type Definitions

typedef void (*esp_ipc_func_t)(void *arg)

  IPC Callback.

  A callback of this type should be provided as an argument when calling esp_ipc_call() or esp_ipc_call_blocking().

### Header File

- components/esp_system/include/esp_ipc_isr.h

- This header file can be included with:

  >     #include "esp_ipc_isr.h"

### Functions

void esp_ipc_isr_call(esp_ipc_isr_func_t func, void *arg)

  Execute an ISR callback on the other CPU.

  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 for XTENSA chips (such as ESP32, ESP32S3). The function is invoked using a CALLX0 instruction and can use only a2, a3, a4 registers. See :doc:`IPC in Interrupt Context </api-reference/system/ipc>` doc for more details.

    - in C or assembly for RISCV chips (such as ESP32P4).

  备注

  This function is not available in single-core mode.

  备注

  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.

  参数:

    - func -- [in] Pointer to a function of type void func(void* arg) to be executed

    - arg -- [in] Arbitrary argument of type void* to be passed into the function

void esp_ipc_isr_call_blocking(esp_ipc_isr_func_t func, void *arg)

  Execute an ISR callback on the other CPU and busy-wait until it completes.

  This function is identical to esp_ipc_isr_call() except that this function will busy-wait until the execution of the callback completes.

  备注

  This function is not available in single-core mode.

  备注

  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.

  参数:

    - func -- [in] Pointer to a function of type void func(void* arg) to be executed

    - arg -- [in] Arbitrary argument of type void* to be passed into the function

void esp_ipc_isr_stall_other_cpu(void)

  Stall the other CPU unconditionally.

  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.

  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.

  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.

  备注

  Not available in single-core mode.

  备注

  The caller is responsible for ensuring no deadlocks on spinlocks occur. Use esp_ipc_isr_stall_other_cpu_safe() for safer operation.

esp_err_t esp_ipc_isr_stall_other_cpu_safe(void)

  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.

  备注

  This function is intended for scenarios where safe stalling is required and the state of the other CPU must be checked.

  返回:

    - 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.

bool esp_ipc_isr_is_other_cpu_stalled(void)

  Check whether the other CPU is currently stalled.

  返回:

    true if the other CPU has entered the IPC ISR stall loop, false otherwise.

void esp_ipc_isr_release_other_cpu(void)

  Release the other CPU.

  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

  备注

  This function is not available in single-core mode.

void esp_ipc_isr_stall_pause(void)

  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_abort(void)

  Abort a CPU stall.

  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().

  - This function is used in panic handling code

void esp_ipc_isr_stall_resume(void)

  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.

### Macros

esp_ipc_isr_asm_call(func, arg)

  Execute an ISR callback on the other CPU See esp_ipc_isr_call().

esp_ipc_isr_asm_call_blocking(func, arg)

  Execute an ISR callback on the other CPU and busy-wait until it completes See esp_ipc_isr_call_blocking().

### Type Definitions

typedef void (*esp_ipc_isr_func_t)(void *arg)

  IPC ISR Callback.

  The callback must be written:

  - in assembly for XTENSA chips (such as ESP32, ESP32S3).

  - in C or assembly for RISCV chips (such as ESP32P4).

  A callback of this type should be provided as an argument when calling esp_ipc_isr_call() or esp_ipc_isr_call_blocking().

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。