From a00bb2d1cbe85f69f0d1b85a03bc54203b570d93 Mon Sep 17 00:00:00 2001 From: Konstantin Kondrashov Date: Thu, 12 Feb 2026 17:52:19 +0200 Subject: [PATCH] fix(xtensa): Reset zero overhead loop counter in ISRs --- components/bt/controller/esp32/hli_vectors.S | 11 +- .../test_apps/freertos/port/test_fpu_in_isr.c | 18 +-- .../freertos/port/test_xtensa_lcount_isr.c | 119 ++++++++++++++++++ .../port/test_xtensa_loadstore_handler.c | 2 +- components/xtensa/xtensa_context.S | 6 +- 5 files changed, 143 insertions(+), 13 deletions(-) create mode 100644 components/freertos/test_apps/freertos/port/test_xtensa_lcount_isr.c diff --git a/components/bt/controller/esp32/hli_vectors.S b/components/bt/controller/esp32/hli_vectors.S index 25a631d7ede..7e6681f6c8d 100644 --- a/components/bt/controller/esp32/hli_vectors.S +++ b/components/bt/controller/esp32/hli_vectors.S @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,7 @@ #include #include #include +#include "xtensa/config/core-isa.h" #include "xtensa_context.h" #include "sdkconfig.h" #include "soc/soc.h" @@ -87,12 +88,16 @@ xt_highint4: s32i a2, a0, 4 rsr a2, XT_REG_SAR s32i a2, a0, 8 + #if XCHAL_HAVE_LOOPS rsr a2, XT_REG_LBEG s32i a2, a0, 12 rsr a2, XT_REG_LEND s32i a2, a0, 16 - rsr a2, XT_REG_LCOUNT + /* Save and disable active zero-overhead loop state while executing high-level ISR code. */ + movi a2, 0 + xsr a2, XT_REG_LCOUNT /* a2 = LCOUNT, LCOUNT = 0 */ s32i a2, a0, 20 + #endif rsr a2, EPC1 s32i a2, a0, 24 @@ -189,12 +194,14 @@ _highint4_stack_switch: movi a0, _l4_save_ctx + SPECREG_OFFSET l32i a2, a0, 8 wsr a2, XT_REG_SAR + #if XCHAL_HAVE_LOOPS l32i a2, a0, 12 wsr a2, XT_REG_LBEG l32i a2, a0, 16 wsr a2, XT_REG_LEND l32i a2, a0, 20 wsr a2, XT_REG_LCOUNT + #endif l32i a2, a0, 24 wsr a2, EPC1 diff --git a/components/freertos/test_apps/freertos/port/test_fpu_in_isr.c b/components/freertos/test_apps/freertos/port/test_fpu_in_isr.c index f86bec14ac1..4b472c9ece0 100644 --- a/components/freertos/test_apps/freertos/port/test_fpu_in_isr.c +++ b/components/freertos/test_apps/freertos/port/test_fpu_in_isr.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -60,7 +60,7 @@ Expected: TEST_CASE("FPU: Usage in level 1 ISR", "[freertos]") { intr_handle_t isr_handle; - TEST_ASSERT_EQUAL(ESP_OK, esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, &fpu_isr, NULL, &isr_handle)); + TEST_ESP_OK(esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, &fpu_isr, NULL, &isr_handle)); /* Use the FPU (calculate a different value than in the ISR) - We test using a calculation that will cause a change in mantissa and exponent for extra thoroughness @@ -85,8 +85,9 @@ TEST_CASE("FPU: Usage in level 1 ISR", "[freertos]") // We allow a 0.1% delta on the final result in case of any loss of precision from floating point calculations TEST_ASSERT_FLOAT_WITHIN(0.00001f, 0.01f, test_float); - // Free the ISR - esp_intr_free(isr_handle); + // Free the ISR for ongoing tests + TEST_ESP_OK(esp_intr_free(isr_handle)); + vTaskDelay(10); // Short delay to allow task memory to be freed } /* ------------------------------------------------------------------------------------------------------------------ */ @@ -125,11 +126,9 @@ static void unpinned_task(void *arg) // Allocate an ISR to use the FPU intr_handle_t isr_handle; - TEST_ASSERT_EQUAL(ESP_OK, esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, &fpu_isr, NULL, &isr_handle)); + TEST_ESP_OK(esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, &fpu_isr, NULL, &isr_handle)); // Trigger the ISR xt_set_intset(1 << SW_ISR_LEVEL_1); - // Free the ISR - esp_intr_free(isr_handle); // Task should remain unpinned after the ISR uses the FPU #if !CONFIG_FREERTOS_UNICORE @@ -139,13 +138,16 @@ static void unpinned_task(void *arg) TEST_ASSERT_EQUAL(tskNO_AFFINITY, xTaskGetCoreID(NULL)); #endif #endif // !CONFIG_FREERTOS_UNICORE - // Reenable scheduling/preemption + // Re-enable scheduling/preemption #if ( ( CONFIG_FREERTOS_SMP ) && ( !CONFIG_FREERTOS_UNICORE ) ) vTaskPreemptionEnable(NULL); #else xTaskResumeAll(); #endif + // Free the ISR for ongoing tests + TEST_ESP_OK(esp_intr_free(isr_handle)); + // Indicate done and self delete xTaskNotifyGive((TaskHandle_t)arg); vTaskDelete(NULL); diff --git a/components/freertos/test_apps/freertos/port/test_xtensa_lcount_isr.c b/components/freertos/test_apps/freertos/port/test_xtensa_lcount_isr.c new file mode 100644 index 00000000000..f3121536622 --- /dev/null +++ b/components/freertos/test_apps/freertos/port/test_xtensa_lcount_isr.c @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" + +#if CONFIG_IDF_TARGET_ARCH_XTENSA + +#include +#include "esp_intr_alloc.h" +#include "esp_rom_sys.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "xtensa/config/core-isa.h" +#include "xtensa/xt_specreg.h" +#include "xtensa_api.h" + +#if XCHAL_HAVE_LOOPS + +#define SW_ISR_LEVEL_1 7 +#define SW_ISR_MASK (1U << SW_ISR_LEVEL_1) + +static volatile uint32_t s_isr_body_hits; + +static inline uint32_t read_lbeg(void) +{ + uint32_t val; + __asm__ volatile("rsr.lbeg %0" : "=r"(val)); + return val; +} + +static inline uint32_t read_lend(void) +{ + uint32_t val; + __asm__ volatile("rsr.lend %0" : "=r"(val)); + return val; +} + +static inline uint32_t read_lcount(void) +{ + uint32_t val; + __asm__ volatile("rsr.lcount %0" : "=r"(val)); + return val; +} + +/* + * Re-entry reproducer: + * - Task call: program loop regs via loopnez, trigger SW interrupt from loop body. + * - ISR call: enter same function but skip loopnez (do not program loop regs). + * If stale LCOUNT/LBEG/LEND leaked into ISR, reaching old LEND will loop. + */ +static uint32_t IRAM_ATTR __attribute__((noinline, noclone)) zol_reentry_probe(uint32_t setup_loop) +{ + uint32_t body_hits = 0; + uint32_t loop_count = 256; + uint32_t trigger_once = setup_loop == 1; + + esp_rom_printf("From %s: setup_loop=%u\n", xPortInIsrContext() ? "ISR" : "TASK", setup_loop); + esp_rom_printf("LBEG=0x%08x LEND=0x%08x LCOUNT=0x%08x\n", read_lbeg(), read_lend(), read_lcount()); + + __asm__ volatile( + "beqz %[setup], 1f\n" + "loopnez %[cnt], .loopEnd\n" + "addi %[hits], %[hits], 1\n" + "beqz %[trigger], 1f\n" + "wsr.intset %[int_mask]\n" // trigger SW interrupt + "movi %[trigger], 0\n" + "1:\n" // if setup == 0, skip loopnez and LCOUNT should be 0. + "nop\n" + ".loopEnd:\n" + : [hits] "+r"(body_hits), + [cnt] "+r"(loop_count), + [trigger] "+r"(trigger_once) + : [setup] "r"(setup_loop), + [int_mask] "r"(SW_ISR_MASK) + : "memory" + ); + + return body_hits; +} + +static void IRAM_ATTR lcount_probe_isr(void *arg) +{ + TaskHandle_t waiter_task = (TaskHandle_t)arg; + BaseType_t should_yield = pdFALSE; + + xt_set_intclear(SW_ISR_MASK); + s_isr_body_hits = zol_reentry_probe(0); + + vTaskNotifyGiveFromISR(waiter_task, &should_yield); + if (should_yield == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +TEST_CASE("Xtensa: ISR does not inherit active ZOL state", "[freertos]") +{ + intr_handle_t isr_handle = NULL; + TaskHandle_t this_task = xTaskGetCurrentTaskHandle(); + + xt_set_intclear(SW_ISR_MASK); + TEST_ESP_OK(esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, lcount_probe_isr, (void *)this_task, &isr_handle)); + + s_isr_body_hits = 0xFFFF; + uint32_t task_body_hits = zol_reentry_probe(1); + + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000))); + TEST_ESP_OK(esp_intr_free(isr_handle)); + + TEST_ASSERT_EQUAL_UINT32(0, s_isr_body_hits); + TEST_ASSERT_EQUAL_UINT32(256, task_body_hits); + vTaskDelay(10); // Short delay to allow task memory to be freed +} + +#endif /* XCHAL_HAVE_LOOPS */ +#endif /* CONFIG_IDF_TARGET_ARCH_XTENSA */ diff --git a/components/freertos/test_apps/freertos/port/test_xtensa_loadstore_handler.c b/components/freertos/test_apps/freertos/port/test_xtensa_loadstore_handler.c index 704ce3dab24..a6d314d9aa7 100644 --- a/components/freertos/test_apps/freertos/port/test_xtensa_loadstore_handler.c +++ b/components/freertos/test_apps/freertos/port/test_xtensa_loadstore_handler.c @@ -199,7 +199,7 @@ static void run_sw_isr_level(int int_level) TEST_ASSERT_EQUAL_UINT16(2, s_iram->b16); TEST_ASSERT_EQUAL_UINT32(0, s_iram->b32); - esp_intr_free(handle); + TEST_ESP_OK(esp_intr_free(handle)); } TEST_CASE("LoadStore: 8/16-bit field access in IRAM from ISRs when pending interrupts are present", "[freertos]") diff --git a/components/xtensa/xtensa_context.S b/components/xtensa/xtensa_context.S index 05b962710ad..4a6239abe3a 100644 --- a/components/xtensa/xtensa_context.S +++ b/components/xtensa/xtensa_context.S @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: MIT * - * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2016-2026 Espressif Systems (Shanghai) CO LTD */ /* * Copyright (c) 2015-2019 Cadence Design Systems, Inc. @@ -135,7 +135,9 @@ _xt_context_save: s32i a3, sp, XT_STK_LBEG rsr a3, XT_REG_LEND s32i a3, sp, XT_STK_LEND - rsr a3, XT_REG_LCOUNT + /* Save and disable active zero-overhead loop state while ISR/exception code runs. */ + movi a3, 0 + xsr a3, XT_REG_LCOUNT /* a3 = LCOUNT, LCOUNT = 0 */ s32i a3, sp, XT_STK_LCOUNT #endif