Merge branch 'fix/zol_reset_lcounter' into 'master'

fix(xtensa): Reset zero overhead loop counter in ISRs

Closes IDF-15322

See merge request espressif/esp-idf!45865
This commit is contained in:
Konstantin Kondrashov
2026-04-29 12:44:00 +03:00
5 changed files with 143 additions and 13 deletions

View File

@@ -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 <xtensa/coreasm.h>
#include <xtensa/corebits.h>
#include <xtensa/config/system.h>
#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

View File

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

View File

@@ -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 <stdint.h>
#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 */

View File

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

View File

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