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 5ab62dc82e..2a276ab99a 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -124,6 +124,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", "esp32p4", "esp32c5"] 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..030577e921 --- /dev/null +++ b/examples/system/nmi_isr/main/nmi_isr_main.c @@ -0,0 +1,64 @@ +/* + * 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"); + + gpio_reset_pin(EXAMPLE_GPIO_IN); + /* 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