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.
This commit is contained in:
Omar Chebib 2024-08-12 13:57:46 +08:00
parent 966f2c6a5b
commit 928859307f
16 changed files with 197 additions and 8 deletions

View File

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

View File

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

View File

@ -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 } },

View File

@ -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 <https://www.cadence.com/content/dam/cadence-www/global/en_US/documents/tools/ip/tensilica-ip/isa-summary.pdf>`_.
See :example:`system/nmi_isr` for an example of how to implement a custom NMI handler on Xtensa-based targets.

View File

@ -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"]

View File

@ -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)

View File

@ -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
```

View File

@ -0,0 +1,4 @@
idf_component_register(SRCS "nmi_isr_main.c"
"asm_funcs.S"
INCLUDE_DIRS "."
WHOLE_ARCHIVE)

View File

@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <xtensa/coreasm.h>
#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

View File

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

View File

@ -0,0 +1,63 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#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");
}

View File

@ -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')

View File

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

View File

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

View File

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