From 928859307f92faf131f1d55ce0f8a0d6d6784b54 Mon Sep 17 00:00:00 2001 From: Omar Chebib Date: Mon, 12 Aug 2024 13:57:46 +0800 Subject: [PATCH] fix(esp_hw_support): make the NMI interrupts available for the main application Closes https://github.com/espressif/esp-idf/issues/13629 NMI interrupt level has been freed for all the Xtensa targets, making it possible for the main application to use it. An example has been added to show how to proceed. --- .../esp_hw_support/port/esp32/esp_cpu_intr.c | 2 +- .../port/esp32s2/esp_cpu_intr.c | 2 +- .../port/esp32s3/esp_cpu_intr.c | 2 +- docs/en/api-guides/hlinterrupts.rst | 2 + examples/system/.build-test-rules.yml | 5 ++ examples/system/nmi_isr/CMakeLists.txt | 6 ++ examples/system/nmi_isr/README.md | 40 ++++++++++++ examples/system/nmi_isr/main/CMakeLists.txt | 4 ++ examples/system/nmi_isr/main/asm_funcs.S | 47 ++++++++++++++ examples/system/nmi_isr/main/example_gpio.h | 9 +++ examples/system/nmi_isr/main/nmi_isr_main.c | 63 +++++++++++++++++++ examples/system/nmi_isr/pytest_nmi_isr.py | 13 ++++ examples/system/nmi_isr/sdkconfig.defaults | 0 .../esp_intr_dump/expected_output/esp32.txt | 4 +- .../esp_intr_dump/expected_output/esp32s2.txt | 2 +- .../esp_intr_dump/expected_output/esp32s3.txt | 4 +- 16 files changed, 197 insertions(+), 8 deletions(-) create mode 100644 examples/system/nmi_isr/CMakeLists.txt create mode 100644 examples/system/nmi_isr/README.md create mode 100644 examples/system/nmi_isr/main/CMakeLists.txt create mode 100644 examples/system/nmi_isr/main/asm_funcs.S create mode 100644 examples/system/nmi_isr/main/example_gpio.h create mode 100644 examples/system/nmi_isr/main/nmi_isr_main.c create mode 100644 examples/system/nmi_isr/pytest_nmi_isr.py create mode 100644 examples/system/nmi_isr/sdkconfig.defaults diff --git a/components/esp_hw_support/port/esp32/esp_cpu_intr.c b/components/esp_hw_support/port/esp32/esp_cpu_intr.c index 5ac8c8f704..dd4b1b2e55 100644 --- a/components/esp_hw_support/port/esp32/esp_cpu_intr.c +++ b/components/esp_hw_support/port/esp32/esp_cpu_intr.c @@ -167,7 +167,7 @@ const static intr_desc_t intr_desc_table [SOC_CPU_INTR_NUM] = { [11] = { 3, ESP_CPU_INTR_TYPE_NA, { ESP_CPU_INTR_DESC_FLAG_SPECIAL, ESP_CPU_INTR_DESC_FLAG_SPECIAL } }, [12] = { 1, ESP_CPU_INTR_TYPE_LEVEL, { 0, 0 } }, [13] = { 1, ESP_CPU_INTR_TYPE_LEVEL, { 0, 0 } }, - [14] = { 7, ESP_CPU_INTR_TYPE_LEVEL, { ESP_CPU_INTR_DESC_FLAG_RESVD, ESP_CPU_INTR_DESC_FLAG_RESVD } }, // NMI + [14] = { 7, ESP_CPU_INTR_TYPE_LEVEL, { 0, 0 } }, // NMI #if CONFIG_FREERTOS_CORETIMER_1 [15] = { 3, ESP_CPU_INTR_TYPE_NA, { ESP_CPU_INTR_DESC_FLAG_RESVD, ESP_CPU_INTR_DESC_FLAG_RESVD } }, #else diff --git a/components/esp_hw_support/port/esp32s2/esp_cpu_intr.c b/components/esp_hw_support/port/esp32s2/esp_cpu_intr.c index 34979086e6..0175e6bffd 100644 --- a/components/esp_hw_support/port/esp32s2/esp_cpu_intr.c +++ b/components/esp_hw_support/port/esp32s2/esp_cpu_intr.c @@ -44,7 +44,7 @@ const static intr_desc_t intr_desc_table [SOC_CPU_INTR_NUM] = { [12] = { 1, ESP_CPU_INTR_TYPE_LEVEL, 0 }, [13] = { 1, ESP_CPU_INTR_TYPE_LEVEL, 0 }, /* Interrupt 14 reserved for NMI (Non-Maskable Interrupts) */ - [14] = { 7, ESP_CPU_INTR_TYPE_LEVEL, ESP_CPU_INTR_DESC_FLAG_RESVD }, + [14] = { 7, ESP_CPU_INTR_TYPE_LEVEL, 0 }, #if CONFIG_FREERTOS_CORETIMER_1 [15] = { 3, ESP_CPU_INTR_TYPE_NA, ESP_CPU_INTR_DESC_FLAG_RESVD }, #else diff --git a/components/esp_hw_support/port/esp32s3/esp_cpu_intr.c b/components/esp_hw_support/port/esp32s3/esp_cpu_intr.c index 7c0ef80acc..9fb0e2e5e9 100644 --- a/components/esp_hw_support/port/esp32s3/esp_cpu_intr.c +++ b/components/esp_hw_support/port/esp32s3/esp_cpu_intr.c @@ -42,7 +42,7 @@ const static intr_desc_t intr_desc_table [SOC_CPU_INTR_NUM] = { [12] = { 1, ESP_CPU_INTR_TYPE_LEVEL, { 0, 0 } }, [13] = { 1, ESP_CPU_INTR_TYPE_LEVEL, { 0, 0 } }, /* Interrupt 14 reserved for NMI (Non-Maskable Interrupts) */ - [14] = { 7, ESP_CPU_INTR_TYPE_LEVEL, { ESP_CPU_INTR_DESC_FLAG_RESVD, ESP_CPU_INTR_DESC_FLAG_RESVD } }, // NMI + [14] = { 7, ESP_CPU_INTR_TYPE_LEVEL, { 0, 0 } }, // NMI [15] = { 3, ESP_CPU_INTR_TYPE_NA, { ESP_CPU_INTR_DESC_FLAG_SPECIAL, ESP_CPU_INTR_DESC_FLAG_SPECIAL } }, [16] = { 5, ESP_CPU_INTR_TYPE_NA, { ESP_CPU_INTR_DESC_FLAG_SPECIAL, ESP_CPU_INTR_DESC_FLAG_SPECIAL } }, [17] = { 1, ESP_CPU_INTR_TYPE_LEVEL, { 0, 0 } }, diff --git a/docs/en/api-guides/hlinterrupts.rst b/docs/en/api-guides/hlinterrupts.rst index c7ef140dc0..6655a13ae4 100644 --- a/docs/en/api-guides/hlinterrupts.rst +++ b/docs/en/api-guides/hlinterrupts.rst @@ -119,3 +119,5 @@ This will ensure the linker to always includes the file defining ``ld_include_my - In theory, medium priority interrupts could also be handled in this way. ESP-IDF does not support this yet. - To check Xtensa instruction set architecture (ISA), please refer to `Xtensa ISA Summary `_. + +See :example:`system/nmi_isr` for an example of how to implement a custom NMI handler on Xtensa-based targets. diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index f11e7b4ab2..8673346fe7 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -139,6 +139,11 @@ examples/system/light_sleep: disable: - if: SOC_LIGHT_SLEEP_SUPPORTED != 1 +examples/system/nmi_isr: + enable: + - if: IDF_TARGET_ARCH_XTENSA == 1 + reason: test NMI for Xtensa targets only + examples/system/ota/advanced_https_ota: disable: - if: IDF_TARGET in ["esp32h2", "esp32c61"] diff --git a/examples/system/nmi_isr/CMakeLists.txt b/examples/system/nmi_isr/CMakeLists.txt new file mode 100644 index 0000000000..32ead19395 --- /dev/null +++ b/examples/system/nmi_isr/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(nmi_isr) diff --git a/examples/system/nmi_isr/README.md b/examples/system/nmi_isr/README.md new file mode 100644 index 0000000000..93fbf19d68 --- /dev/null +++ b/examples/system/nmi_isr/README.md @@ -0,0 +1,40 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | + +# NMI ISR Example + +This example demonstrates how to allocate and use non-maskable interrupt (NMI) on Xtensa-based targets. The `asm_funcs.S` file contains the ISR that will be run on the core that installed the NMI. The callback should be fairly simple and must be entirely written in assembly. + +Defining an NMI handler can be done by defining a routine named `xt_nmi`. That routine will be called via `call0` instruction, as such, before returning from the ISR, the return address register, `a0`, must be restored thanks to the instruction: + +``` +rsr a0, EXCSAVE + XCHAL_NMILEVEL +``` + +## How to use example + +### Hardware Required + +Example can run on any Xtensa-based ESP32 development board. Since the example uses GPIO19 as a bi-directional pin, make sure not to connect it to anything. + +### Configure the project + +No particular configuration is required to run this example, the default one is suitable. + +### Build and Flash + +``` +idf.py build flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + + +## Example output + +``` +example: Start +example: Success +``` diff --git a/examples/system/nmi_isr/main/CMakeLists.txt b/examples/system/nmi_isr/main/CMakeLists.txt new file mode 100644 index 0000000000..5041b683ea --- /dev/null +++ b/examples/system/nmi_isr/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "nmi_isr_main.c" + "asm_funcs.S" + INCLUDE_DIRS "." + WHOLE_ARCHIVE) diff --git a/examples/system/nmi_isr/main/asm_funcs.S b/examples/system/nmi_isr/main/asm_funcs.S new file mode 100644 index 0000000000..437de583b1 --- /dev/null +++ b/examples/system/nmi_isr/main/asm_funcs.S @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include "soc/gpio_reg.h" +#include "example_gpio.h" + + .global nmi_triggered + + .section .bss +nmi_triggered: + .space 4 + + +/** + * @brief This current ISR was called via `call0` instruction, so `a0` (return address) + * was altered. Fortunately, `a0` was saved in EXCSAVE registers, restore it before + * returning + */ + .section .iram1, "ax" + .align 4 + .global xt_nmi + .type xt_nmi, @function +xt_nmi: + addi sp, sp, -16 + s32i a3, sp, 0 + + /* Set the interrupt flag to 1 */ + movi a0, nmi_triggered + movi a3, 1 + s32i a3, a0, 0 + + /* Set the GPIO level back to low to prevent triggering an interrupt again */ + movi a0, GPIO_OUT_W1TC_REG + movi a3, 1 << EXAMPLE_GPIO_IN + s32i a3, a0, 0 + + /* Restore a3 and a0 before leaving*/ + l32i a3, sp, 0 + addi sp, sp, 16 + rsr a0, EXCSAVE + XCHAL_NMILEVEL + + /* Return from NMI, we need to specify the level */ + rfi XCHAL_NMILEVEL diff --git a/examples/system/nmi_isr/main/example_gpio.h b/examples/system/nmi_isr/main/example_gpio.h new file mode 100644 index 0000000000..22645787be --- /dev/null +++ b/examples/system/nmi_isr/main/example_gpio.h @@ -0,0 +1,9 @@ + +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +#define EXAMPLE_GPIO_IN 19 diff --git a/examples/system/nmi_isr/main/nmi_isr_main.c b/examples/system/nmi_isr/main/nmi_isr_main.c new file mode 100644 index 0000000000..f2e86d5f42 --- /dev/null +++ b/examples/system/nmi_isr/main/nmi_isr_main.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "freertos/FreeRTOSConfig.h" +#include "freertos/FreeRTOS.h" +#include "sdkconfig.h" +#include "driver/gpio.h" +#include "hal/gpio_ll.h" +#include "soc/interrupts.h" +#include "example_gpio.h" + +extern volatile int nmi_triggered; + +extern void xt_nmi(void*); + +void app_main(void) +{ + intr_handle_t handle; + esp_err_t err; + + printf("example: Start\n"); + + /* Make sure we have a pull-down on the input GPIO to prevent noise (when disconnected) */ + gpio_pulldown_en(EXAMPLE_GPIO_IN); + gpio_set_direction(EXAMPLE_GPIO_IN, GPIO_MODE_INPUT_OUTPUT); + + /* Register the interrupt handler as an NMI. When registering high level interrupts, + * the interrupt allocator expects the handler passed as an argument to be NULL. */ + err = esp_intr_alloc(ETS_GPIO_INTR_SOURCE, ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_NMI, NULL, NULL, &handle); + if (err != ESP_OK) { + printf("Failure: could not install NMI ISR, %d(0x%x)\n", err, err); + return; + } + gpio_set_intr_type(EXAMPLE_GPIO_IN, GPIO_INTR_HIGH_LEVEL); + gpio_intr_enable(EXAMPLE_GPIO_IN); + + vTaskDelay(200 / portTICK_PERIOD_MS); + + /* Disable interrupts on the CPU side and make sure the NMI is still triggered */ + const uint32_t mask = esp_cpu_intr_get_enabled_mask(); + esp_cpu_intr_disable(0xFFFFFFFF); + nmi_triggered = 0; + + /* Setting EXAMPLE_GPIO_IN to 1 will trigger the NMI interrupt. */ + gpio_set_level(EXAMPLE_GPIO_IN, 1); + + /* Wait for the interrupt to occur */ + while (nmi_triggered == 0) { + /* We cannot use vTaskDelay since the interrupts are disabled */ + } + + esp_cpu_intr_enable(mask); + + gpio_intr_disable(EXAMPLE_GPIO_IN); + esp_intr_free(handle); + printf("example: Success\n"); +} diff --git a/examples/system/nmi_isr/pytest_nmi_isr.py b/examples/system/nmi_isr/pytest_nmi_isr.py new file mode 100644 index 0000000000..7119f6023d --- /dev/null +++ b/examples/system/nmi_isr/pytest_nmi_isr.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.generic +def test_nmi_isr(dut: Dut) -> None: + dut.expect_exact('example: Start') + dut.expect_exact('example: Success') diff --git a/examples/system/nmi_isr/sdkconfig.defaults b/examples/system/nmi_isr/sdkconfig.defaults new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/test_apps/system/esp_intr_dump/expected_output/esp32.txt b/tools/test_apps/system/esp_intr_dump/expected_output/esp32.txt index f5dfa62656..532a59e02d 100644 --- a/tools/test_apps/system/esp_intr_dump/expected_output/esp32.txt +++ b/tools/test_apps/system/esp_intr_dump/expected_output/esp32.txt @@ -14,7 +14,7 @@ CPU 0 interrupt status: 11 3 Level CPU-internal 12 1 Level Free 13 1 Level Free - 14 7 Level Reserved + 14 7 Level Free 15 3 Level CPU-internal 16 5 Level CPU-internal 17 1 Level Free @@ -48,7 +48,7 @@ CPU 1 interrupt status: 11 3 Level CPU-internal 12 1 Level Free 13 1 Level Free - 14 7 Level Reserved + 14 7 Level Free 15 3 Level CPU-internal 16 5 Level CPU-internal 17 1 Level Free diff --git a/tools/test_apps/system/esp_intr_dump/expected_output/esp32s2.txt b/tools/test_apps/system/esp_intr_dump/expected_output/esp32s2.txt index accd61946a..9f76ae8b8b 100644 --- a/tools/test_apps/system/esp_intr_dump/expected_output/esp32s2.txt +++ b/tools/test_apps/system/esp_intr_dump/expected_output/esp32s2.txt @@ -14,7 +14,7 @@ CPU 0 interrupt status: 11 3 Level CPU-internal 12 1 Level Free 13 1 Level Free - 14 7 Level Reserved + 14 7 Level Free 15 3 Level CPU-internal 16 5 Level CPU-internal 17 1 Level Free diff --git a/tools/test_apps/system/esp_intr_dump/expected_output/esp32s3.txt b/tools/test_apps/system/esp_intr_dump/expected_output/esp32s3.txt index fb288b763e..c50ba0067b 100644 --- a/tools/test_apps/system/esp_intr_dump/expected_output/esp32s3.txt +++ b/tools/test_apps/system/esp_intr_dump/expected_output/esp32s3.txt @@ -14,7 +14,7 @@ CPU 0 interrupt status: 11 3 Level CPU-internal 12 1 Level Free 13 1 Level Free - 14 7 Level Reserved + 14 7 Level Free 15 3 Level CPU-internal 16 5 Level CPU-internal 17 1 Level Free @@ -48,7 +48,7 @@ CPU 1 interrupt status: 11 3 Level CPU-internal 12 1 Level Free 13 1 Level Free - 14 7 Level Reserved + 14 7 Level Free 15 3 Level CPU-internal 16 5 Level CPU-internal 17 1 Level Free